TypeScript Nested keys泛型约束报错:ShortHandKeyMapper定义问题
解决嵌套路径拆分的泛型类型约束问题
问题描述
需要创建一个泛型类型ShortHandKeyMapper,包含两个属性:
control:嵌套路径的前缀部分value:该前缀对应的剩余路径部分
原实现出现“不满足约束”的错误,且无法正确拦截control与value的非法组合(比如错误允许control: 'level1.level2.level3'搭配value: 'level2')。
原代码问题分析
原ShortHandKeyMapper的泛型参数设计导致control和value的类型未关联:
type ShortHandKeyMapper<F extends object, Base extends RecursiveKeyOf<F> = RecursiveKeyOf<F>> = { control: Base value: SubRecursiveKeys<RecursiveKeyOf<F>, Base> }
当未显式指定Base时,它是RecursiveKeyOf<F>的联合类型,此时SubRecursiveKeys会尝试对整个联合类型计算,最终得到never类型,导致value与control的类型不匹配,触发约束错误。同时这种写法无法保证control的具体值和value的子路径一一对应。
解决方案
将ShortHandKeyMapper设计为联合类型,让每个control值对应唯一合法的value类型:
export type RecursiveKeyOf<TObj extends object> = { [TKey in keyof TObj & (string | number)]: TObj[TKey] extends any[] ? `${TKey}` : TObj[TKey] extends object ? `${TKey}` | `${TKey}.${RecursiveKeyOf<TObj[TKey]>}` : `${TKey}`; }[keyof TObj & (string | number)]; type SubRecursiveKeys<RK extends string, PFX extends RK> = RK extends `${PFX}.${infer SubKey}` ? SubKey : never; // 修正后的 ShortHandKeyMapper type ShortHandKeyMapper<F extends object> = { [K in RecursiveKeyOf<F>]: { control: K; value: SubRecursiveKeys<RecursiveKeyOf<F>, K>; }; }[RecursiveKeyOf<F>]; // 测试对象 const a = { level1: { level2: { level3: { level4: { level5: 'test' } } } } }
验证效果
- 非法组合会被TypeScript拦截:
const denyMapper: ShortHandKeyMapper<typeof a> = { control: 'level1.level2.level3', value: 'level2' // 错误:类型'"level2"'不能赋值给类型'"level4" | "level4.level5"' }
- 合法组合正常通过:
const okMapper: ShortHandKeyMapper<typeof a> = { control: 'level1.level2.level3', value: 'level4' // 正常通过 } const okMapper2: ShortHandKeyMapper<typeof a> = { control: 'level1.level2.level3', value: 'level4.level5' // 正常通过 }
可选优化:支持叶子节点的空值
如果希望前缀是叶子路径时,value可以省略,可将value设为可选属性:
type ShortHandKeyMapper<F extends object> = { [K in RecursiveKeyOf<F>]: { control: K; value?: SubRecursiveKeys<RecursiveKeyOf<F>, K>; }; }[RecursiveKeyOf<F>]; // 叶子路径的合法写法 const leafMapper: ShortHandKeyMapper<typeof a> = { control: 'level1.level2.level3.level4.level5' // value 可省略,因为无后续路径 }
内容的提问来源于stack exchange,提问作者Eugen Halca
相关产品推荐
相关产品推荐

