TypeScript中Props<T>[K]解析为联合类型的原因及优化问询
TypeScript泛型映射类型的类型收窄问题
先看我们的代码实现:
interface SubProps1<T> { foo: boolean bar: string baz: T } interface SubProps2<T> { fooz: boolean barr: string bazd: Array<T> } interface Props<T> { sub1: SubProps1<T> sub2: SubProps2<T> } type CSSObjectWithLabel = Record<string, unknown> type StylesConfig<T> = { [K in keyof Props<T>]?: StylesConfigFunction<Props<T>[K]>; } type StylesConfigFunction<SubProps> = (base: CSSObjectWithLabel, props: SubProps) => CSSObjectWithLabel; function mergeStyles<T>( nonColorStyles: StylesConfig<T>, colorStyles: StylesConfig<T>, ): StylesConfig<T> { type S = StylesConfig<T> const result: S = {}; const uniqueKeys = new Set([ ...Object.keys(nonColorStyles), ...Object.keys(colorStyles) ] as (keyof S)[]) function assigner<K extends keyof S>(key: K) { // 这里Props<T>[K]被解析为SubProps1<T> | SubProps2<T>联合类型,导致类型不匹配 result[key] = (base: CSSObjectWithLabel, props: Props<T>[K]): CSSObjectWithLabel => ({ ...base, ...colorStyles[key]?.(base, props), ...nonColorStyles[key]?.(base, props) }) } return result; }
问题说明
尝试合并符合Props接口的键值映射函数时,assigner函数内为result[key]赋值时,Props<T>[K]被解析为SubProps1<T> | SubProps2<T>联合类型。理论上特定key对应的result[key]类型应该匹配对应的StylesConfigFunction,只接受正确的SubProps类型而非联合类型,但TypeScript无法自动做到这一点。
限制原因
TypeScript的类型系统在处理泛型索引类型时,无法跟踪泛型参数K和具体属性类型的一一对应关系:
K是一个泛型参数,代表keyof S的任意子集,而非某个具体的键- 当在函数内部使用
key: K时,TypeScript只能推断出Props<T>[K]是所有可能的SubProps类型的联合,无法将K的具体值和对应的属性类型绑定 - 这是TypeScript类型系统的设计限制,为了保证类型检查的性能和复杂度可控,它不会对泛型索引做更细粒度的关联推断
优化方案
方案一:显式类型断言
直接将赋值的函数断言为StylesConfig<T>[K],明确告诉TypeScript我们确认类型的正确性:
function assigner<K extends keyof S>(key: K) { result[key] = ((base: CSSObjectWithLabel, props: Props<T>[K]) => ({ ...base, ...colorStyles[key]?.(base, props), ...nonColorStyles[key]?.(base, props) })) as StylesConfig<T>[K]; }
方案二:显式声明函数类型
先声明合并后的函数类型为StylesConfigFunction<Props<T>[K]>,让TypeScript明确参数与返回值的类型匹配关系:
function assigner<K extends keyof Props<T>>(key: K) { const mergedFn: StylesConfigFunction<Props<T>[K]> = (base, props) => ({ ...base, ...colorStyles[key]?.(base, props), ...nonColorStyles[key]?.(base, props) }); result[key] = mergedFn; }
方案三:使用reduce简化合并逻辑
通过reduce遍历键,结合类型断言完成合并,代码更简洁:
function mergeStyles<T>( nonColorStyles: StylesConfig<T>, colorStyles: StylesConfig<T>, ): StylesConfig<T> { const uniqueKeys = new Set([ ...Object.keys(nonColorStyles), ...Object.keys(colorStyles) ] as (keyof Props<T>)[]) return [...uniqueKeys].reduce((acc, key) => { acc[key] = (base: CSSObjectWithLabel, props) => ({ ...base, ...colorStyles[key]?.(base, props as Props<T>[typeof key]), ...nonColorStyles[key]?.(base, props as Props<T>[typeof key]) }) as StylesConfig<T>[typeof key]; return acc; }, {} as StylesConfig<T>); }
内容的提问来源于stack exchange,提问作者Wilfried Sugniaux
相关产品推荐
相关产品推荐

