You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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"
//     }
//   ]
// } 

问题原因

  1. Resolved类型定义错误:遍历键N时,错误地使用外层的Name作为InnerNamed的第一个泛型参数,而非当前遍历的具体键N,导致所有条目被推断为统一的Name类型,丢失了具体键名的字面量信息。
  2. 函数泛型约束过于宽松: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 04:50:54