TypeScript无法从查找表推断键值对类型,如何优化?
TypeScript类型推断优化方案
问题描述
我尝试将类型化值的查找表映射为InnerNamed类型的列表,但TypeScript推断出的类型范围过宽。预期resolved的类型为(InnerNamed<"Cefn", 3> | InnerNamed<"Joe", "hey">)[](等价于({Cefn:3} | {Joe:"hey"})[]),但实际推断结果是InnerNamed<string, unknown>[]。
原代码示例
/** 断言Object.entries的类型 */ type InferEntry<Lookup, K extends keyof Lookup = keyof Lookup> = { [K in keyof Lookup]: [K, Lookup[K]]; }[K]; /** 仅包含单个命名条目的对象类型(类似JavaScript记录的常见范式,例如: * ``` * { * MyItem:{some:"value"} * } * ``` */ type InnerNamed<InnerName extends string, T> = { [k in InnerName]: T }; const namedProps: InnerNamed<"Cefn", {hi:number}> = { // 自动补全正常工作 Cefn: { hi: 3 } }; /** 根据命名类型的查找表映射为InnerNamed类型 */ type Resolved<Name extends string, Resolver extends Record<Name, any>> = { [N in Name]: InnerNamed<Name, Resolver[Name]>; }[Name]; function createResolved<Name extends string, Resolver extends Record<Name, any>>(resolver:Resolver){ return Object.entries(resolver).map((entry) => { const [name, value] = entry as InferEntry<typeof resolver> return { [name]:value } }) as Resolved<Name, Resolver>[] } // 预期类型:(InnerNamed<"Cefn", 3> | InnerNamed<"Joe", "hey">)[] // 实际类型:InnerNamed<string, unknown>[] const resolved = createResolved({ Cefn:3, Joe:"hey" }); // 打印结果显示实际结构符合窄类型,但类型推断未生效 console.log({resolved}) // "resolved": [ // { // "Cefn": 3 // }, // { // "Joe": "hey" // } // ] // }
问题原因
Resolved类型定义错误:遍历键N时,错误地使用外层的Name作为InnerNamed的第一个泛型参数,而非当前遍历的具体键N,导致所有条目被推断为统一的Name类型,丢失了具体键名的字面量信息。- 函数泛型约束过于宽松:
Resolver extends Record<Name, any>允许Name被推断为宽泛的string类型,无法保留查找表中每个键的具体类型。
修正方案
步骤1:修正Resolved类型
改为基于具体查找表类型,遍历每个键生成对应的InnerNamed类型:
type Resolved<Lookup extends Record<string, unknown>> = { [N in keyof Lookup]: InnerNamed<N & string, Lookup[N]>; }[keyof Lookup];
步骤2:优化函数泛型与类型断言
调整函数泛型,让TypeScript精确推断查找表的每个键值对类型,同时修正映射时的类型断言:
function createResolved<Lookup extends Record<string, unknown>>(resolver: Lookup) { return Object.entries(resolver).map(([name, value]) => { return { [name]: value } as InnerNamed<typeof name & string, typeof value>; }) as Resolved<Lookup>[]; }
步骤3:(可选)获取字面量级别的值类型
如果需要让值的类型精确到字面量(比如3而非number,"hey"而非string),可以在调用时添加as const断言:
const resolved = createResolved({ Cefn: 3 as const, Joe: "hey" as const });
修正后效果
此时resolved的类型会被正确推断为:(InnerNamed<"Cefn", 3> | InnerNamed<"Joe", "hey">)[],与预期完全一致。
内容的提问来源于stack exchange,提问作者cefn
相关产品推荐
相关产品推荐

