TypeScript中类型Y与Z行为差异及never类型赋值报错问题
问题描述
基础示例
变量z出现编译器错误:'string' is not assignable to type 'never',为何类型Y与Z的定义表现存在差异?
代码如下:
type X = string | undefined type Y<T> = T extends undefined ? string : never type Z = X extends undefined ? string : never const x : X = "x" const x2 : X = undefined const y : Y<string | undefined> = "y" const y2 : Y<X> = "y" const z : Z = "z" // Type 'string' is not assignable to type 'never'
注:使用包裹数组的写法type Z = [X] extends [undefined] ? string : never同样报错
复杂示例
以下示例中foo和bar均出现相同错误:'string' is not assignable to type 'never'
type Example = { foo?: string, bar?: string } type Example_foo = Example["foo"] // string | undefined type ExampleMap<T> = { [P in keyof T]-?: T[P] extends undefined ? string : never } const v : ExampleMap<Example> = { foo: "biz", bar: "baz" } // Type 'string' is not assignable to type 'never'
原因解析
1. 基础示例中Y与Z的差异
类型Y:分布式条件类型
当条件类型的extends左侧是泛型参数时,TypeScript会触发分布式条件类型逻辑:如果泛型参数是联合类型,会自动将联合类型拆分为单个成员分别进行条件判断,最后将结果重新合并为联合类型。
对于Y<string | undefined>,TypeScript会拆分处理:string extends undefined ? string : never→neverundefined extends undefined ? string : never→string
最终合并结果为never | string(等价于string),所以y和y2可以正常赋值字符串。
类型Z:非分布式条件类型
当extends左侧不是泛型参数,而是具体的联合类型X(即string | undefined)时,不会触发分布式逻辑。此时判断的是整个联合类型是否完全extends undefined,显然string | undefined包含string成员,不满足条件,所以Z的最终类型是never,赋值字符串时自然报错。
用[X] extends [undefined]包裹后,实际是判断数组类型[string | undefined]是否extends[undefined],前者的元素类型包含string,同样不满足条件,结果还是never,报错依然存在。
2. 复杂示例的错误原因
ExampleMap<T>中的T[P] extends undefined不会触发分布式逻辑:这里的T[P]是确定的联合类型string | undefined,并非泛型参数。此时TypeScript会直接判断整个联合类型是否完全extendsundefined,显然不成立,所以结果为never,导致ExampleMap<Example>中foo和bar的类型都是never,赋值字符串时触发错误。
如果要实现预期效果,需要引入泛型触发分布式逻辑,比如:
type UndefinedToString<U> = U extends undefined ? string : never type ExampleMap<T> = { [P in keyof T]-?: UndefinedToString<T[P]> }
此时UndefinedToString<T[P]>会拆分string | undefined进行判断,最终得到string类型,就能正常赋值了。
内容的提问来源于stack exchange,提问作者kelaban

