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

TypeScript模板字面量作为对象键的类型推断异常问题及解法

问题描述

第一个示例中,对象键的类型推断符合预期:

const a = 'a'
const obj = { [a]: 1 } // 类型为 { a: 1 }

但第二个示例出现类型推断异常:

const b = 'b/a' as `b/${string}`
const obj2 = { [b]: 1 } // 实际类型为 { [x: string]: 1 }, 期望类型为 { [x: `b/${string}`]: 1 }

const c = <T extends Record<string, unknown>>(arg: T): (keyof T & string)[] => {
    return Object.keys(arg) 
}

const d = c(obj2) // 实际类型为 string[], 期望类型为 (`b/${string}`)[]
原因分析

TypeScript对计算属性的类型推断有明确规则:

  • 当计算属性的键是具体字符串字面量(比如第一个示例中的'a'),TypeScript会保留该具体字符串作为对象的键类型;
  • 当计算属性的键是宽字面量类型(比如b/${string}这种包含通配符的模板字面量类型),TypeScript会默认将其退化为string类型的索引签名——因为编译期无法确定键的具体值,只能推断出键属于string范畴。

另外,Object.keys()的返回类型被TypeScript硬编码为string[],这是因为JavaScript中对象的键在运行时总会被转换为字符串,TypeScript无法保证静态类型中的键类型与运行时完全一致,所以默认返回最安全的string[]。

解决方法

方法1:使用泛型辅助函数创建对象

通过泛型约束让TypeScript保留键的模板字面量类型:

function createTypedObj<K extends string>(key: K, value: number): { [P in K]: number } {
  return { [key]: value } as { [P in K]: number };
}

const b = 'b/a' as `b/${string}`;
const obj2 = createTypedObj(b, 1); // 类型为 { [x: `b/${string}`]: number }

const c = <T extends Record<string, unknown>>(arg: T): (keyof T & string)[] => {
  return Object.keys(arg) as (keyof T & string)[]; // 手动断言类型
};

const d = c(obj2); // 类型为 (`b/${string}`)[]

方法2:直接断言对象类型

如果不想写辅助函数,可以直接对创建的对象进行类型断言:

const b = 'b/a' as `b/${string}`;
const obj2 = { [b]: 1 } as { [K in typeof b]: number }; // 类型为 { [x: `b/${string}`]: number }

const c = <T extends Record<string, unknown>>(arg: T): (keyof T & string)[] => {
  return Object.keys(arg) as (keyof T & string)[];
};

const d = c(obj2); // 类型为 (`b/${string}`)[]

需要注意:对Object.keys()的返回值进行类型断言是必要的,这是TypeScript语言设计上的保守策略——避免运行时出现类型不匹配的风险,所以无法自动将返回值收窄为keyof T类型。

内容的提问来源于stack exchange,提问作者Acid Coder

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 13:48:22