TypeScript:使用UnionToIntersection时保留子表达式的问题
TypeScript可空类型解析为never的问题解决方法
问题背景
以下是简化自CSV写入库的代码,核心问题是SpecToValue<['nullable', 'string']>被解析为never,而非预期的string | null,导致传入合法值时触发错误:
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; type Spec = 'string' | 'timestamp' | ['nullable', Spec]; type SpecToValue<T> = UnionToIntersection< T extends 'string' ? string : T extends 'timestamp' ? Date : T extends ['nullable', infer InnerT] ? SpecToValue<InnerT> | null : never >; declare function writeValue<T extends Spec>(spec: T, value: SpecToValue<T>): void; function test(s1: 'string', s2: 'string' | 'timestamp', s3: ['nullable', 'string']) { writeValue(s1, 'hello'); writeValue(s1, false); // error (正确) writeValue(s1, null); // error (正确) writeValue(s2, 'hello'); // error (正确) writeValue(s2, false); // error (正确) writeValue(s2, null); // error (正确) // 问题:此处期望允许传入'string'或null,但实际报错 writeValue(s3, 'hello'); // ERROR (错误) writeValue(s3, false); // error (正确) writeValue(s3, null); // ERROR (错误) }
问题根源在于UnionToIntersection会将联合类型转换为交叉类型:当处理可空分支时,SpecToValue<InnerT> | null会被转换成string & null,最终结果为never。
补充定义(实际代码结构)
实际代码中将简单类型与可空类型分离:
type SimpleSpec = 'string' | 'timestamp'; type Spec = SimpleSpec | ['nullable', SimpleSpec]; type SimpleSpecToValue<T> = T extends 'string' ? string : T extends 'timestamp' ? Date : never; type SpecToValue<T> = T extends SimpleSpec ? SimpleSpecToValue<T> : T extends ['nullable', infer InnerT] ? (SpecToValue<InnerT> | null) : null
解决方案
调整类型逻辑,仅对简单类型的联合应用UnionToIntersection,保留可空类型的联合特性:
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; type SimpleSpec = 'string' | 'timestamp'; type Spec = SimpleSpec | ['nullable', SimpleSpec]; type SimpleSpecToValue<T> = T extends 'string' ? string : T extends 'timestamp' ? Date : never; // 仅针对SimpleSpec的联合类型应用交叉转换,确保联合类型的Spec返回never type ResolveSimpleSpec<T extends SimpleSpec> = UnionToIntersection<SimpleSpecToValue<T>>; type SpecToValue<T> = T extends SimpleSpec ? ResolveSimpleSpec<T> : // 处理可空类型时直接返回联合,不应用交叉转换 T extends ['nullable', infer InnerT] ? InnerT extends SimpleSpec ? (ResolveSimpleSpec<InnerT> | null) : never : never; declare function writeValue<T extends Spec>(spec: T, value: SpecToValue<T>): void; function test(s1: 'string', s2: 'string' | 'timestamp', s3: ['nullable', 'string']) { writeValue(s1, 'hello'); // 正常 writeValue(s1, false); // error (正确) writeValue(s1, null); // error (正确) writeValue(s2, 'hello'); // error (正确,联合类型无合法值) writeValue(s2, false); // error (正确) writeValue(s2, null); // error (正确) writeValue(s3, 'hello'); // 正常 (符合预期) writeValue(s3, false); // error (正确) writeValue(s3, null); // 正常 (符合预期) }
逻辑说明
ResolveSimpleSpec:仅对SimpleSpec类型应用UnionToIntersection,当传入'string' | 'timestamp'这类联合类型时,会得到string & Date即never,确保无法传入任何合法值。- 可空类型处理:直接返回
ResolveSimpleSpec<InnerT> | null的联合类型,避免交叉转换破坏可空特性。
内容的提问来源于stack exchange,提问作者Kannan Goundan
相关产品推荐
相关产品推荐

