泛型keyof T结合Extract工具类型出现不可赋值问题求助
先看问题中的代码:
type MyAnimals = { name: "dog", fleas: 2 } | { name: "cat" } function GetKeys<T extends MyAnimals>(animal: T) { for (let k in animal) { let sansAssertion = k; sansAssertion = 'name'; // 报错 -> 为什么? sansAssertion = 'fleas'; // 报错 -> 这个我理解... sansAssertion = 'hohoho'; // 报错 -> 这就离谱了 const thisIsConst = 'name' as const; // 类型是'name',绝对不是string sansAssertion = thisIsConst; // 报错 -> 搞什么?还是不行? let withAssertion = k as keyof T; withAssertion = 'name'; // 正常 -> 哦,原来如此! withAssertion = 'fleas'; // 报错 -> 合理 withAssertion = 'hohoho'; // 报错 -> 离谱 let letsBeExplicit: Extract<keyof T, string> = 'name'; // 报错 -> 到这已经预料到了。 // 到底能给这个变量赋值什么? type SpelledOut = Extract<('name' | 'fleas') | 'name', string> let noGeneric: SpelledOut = 'name'; // 正常 -> 对,不是Extract本身有问题 type LessSpelledOut = Extract<keyof ({ name: string, fleas: number} | { name: string }), string>; let fromKeys: LessSpelledOut = 'name'; // 正常 -> 没问题 fromKeys = 'fleas' // 报错 -> 没问题 } }
核心原因拆解
1. 泛型未具体化时,条件类型无法做赋值检查
Extract<keyof T, string>是依赖泛型T的条件类型。由于T extends MyAnimals,T可以是MyAnimals的任意子类型:
- 如果
T是{ name: "cat" },Extract<keyof T, string>等于'name' - 如果
T是{ name: "dog", fleas: 2 },结果是'name' | 'fleas'
在泛型函数内部,TypeScript无法提前确定T的具体类型,因此不会将任何字面量(比如'name')判定为兼容于这个不确定的条件类型——毕竟如果T是前者,赋值'fleas'会出错;如果是后者,赋值'name'没问题,但TypeScript不会冒这个风险,干脆直接禁止所有赋值操作。
2. for...in中k的类型推断规则
在泛型函数的for...in循环里,k的类型被推断为Extract<keyof T, string>,而非普通的string。这就是给sansAssertion赋值'name'报错的核心原因——本质还是泛型条件类型的不确定性问题。
3. 非泛型场景正常的原因
当你在非泛型代码中写Extract<keyof ({ name: string, fleas: number} | { name: string }), string>时,TypeScript可以直接计算出结果:keyof (A | B)等于keyof A & keyof B(即两个类型的共有键),也就是'name'。此时Extract的结果是确定的字面量类型,自然可以正常赋值。
4. k as keyof T能赋值'name'的原因
keyof T在T extends MyAnimals的约束下,必然包含'name'——因为所有MyAnimals的子类型都有name属性。TypeScript能确定这一点,所以允许你把'name'赋值给keyof T类型的变量。而'fleas'只存在于部分子类型中,所以赋值会报错,这符合预期。
内容的提问来源于stack exchange,提问作者NoBullsh1t

