TypeScript动态泛型:如何根据id值自动推导defaultValue属性类型
根据对象字段自动推导关联字段类型的PropConfig实现
问题背景
需求为配置对象的defaultValue字段可根据同对象内id字段的取值,自动匹配对应的类型约束。
初始实现需要手动传入第二个泛型参数才能获得正确的类型校验,写法如下:
type User = { name: string; age: number; }; type PropConfig<T, K extends keyof T> = { id: K; label: string; defaultValue: T[K]; }; // 需要手动传入第二个泛型参数'age'才能校验defaultValue类型 const config: PropConfig<User, 'age'> = { id: 'age', label: 'Age', defaultValue: 18, };
尝试给第二个泛型加默认值的写法会出现类型校验失效问题,无法实现自动推导:
type PropConfig<T, K = keyof T> = { id: K; label: string; defaultValue: T[K]; // defaultValue会被推导为所有字段值的联合类型,无法精准匹配 }; const config: PropConfig<User> = { id: 'age', label: 'Age', defaultValue: 18, };
原有写法失效原因
给泛型K设置默认值K = keyof T时,如果不手动传入K,TS会直接将K填充为keyof T即目标类型所有字段名的联合类型(对上述User类型来说就是'name' | 'age'),此时defaultValue的类型会被推导为T[keyof T]即所有字段值的联合类型(string | number),无法根据对象字面量里实际赋值的id字段做类型收窄,自然无法实现精准校验。
可落地的实现方案
方案1:泛型辅助函数(推荐,适配复杂场景)
TS不会在变量类型标注阶段自动推导未指定的泛型参数,但调用泛型函数时,可以根据传入的参数值自动推导泛型的具体类型,只需要写一个通用的辅助函数即可实现自动推导,不需要额外传第二个泛型。
type User = { name: string; age: number; }; type PropConfig<T, K extends keyof T> = { id: K; label: string; defaultValue: T[K]; }; // 定义泛型辅助函数,K的类型由传入的配置对象自动推导 function definePropConfig<T extends object, K extends keyof T>(config: PropConfig<T, K>) { return config; } // 使用时仅需传入第一个泛型参数,id和defaultValue自动做类型绑定 const ageConfig = definePropConfig<User>({ id: 'age', label: 'Age', defaultValue: 18, // 类型校验为number,传字符串会直接报错 }); const nameConfig = definePropConfig<User>({ id: 'name', label: 'Name', defaultValue: 'Zhang San', // 类型校验为string,传数字会直接报错 });
方案2:映射类型生成联合类型(无额外函数,轻量场景适用)
如果不想引入额外的辅助函数,可以通过映射类型遍历目标类型的所有key,生成每个key对应的合法配置类型,最后通过索引取所有配置类型的联合。TS校验对象字面量时,会自动根据id字段匹配对应的类型分支,实现类型收窄。
type User = { name: string; age: number; }; // 遍历T的所有key生成对应配置,最后取联合类型 type PropConfig<T> = { [K in keyof T]: { id: K; label: string; defaultValue: T[K]; } }[keyof T]; // 直接标注类型即可,不需要传第二个泛型 const ageConfig: PropConfig<User> = { id: 'age', label: 'Age', defaultValue: 18, // 正确,类型为number }; const nameConfig: PropConfig<User> = { id: 'name', label: 'Name', defaultValue: 'Zhang San', // 正确,类型为string };
内容的提问来源于stack exchange,提问作者Harry Duong
相关产品推荐
相关产品推荐

