TypeScript如何通过变量访问对象属性同时保留强类型约束
问题核心
报错的根本原因是TypeScript无法确认你持有的索引变量属于目标类型的合法属性名集合,直接给类型添加宽泛的[key: string]: xxx索引签名会彻底放开属性名校验,完全破坏类型安全,不是合理解法。
正确实现方式
核心思路是把索引变量的类型约束为目标类型的合法键类型,TS提供了keyof操作符可以直接拿到某个类型的所有合法属性名组成的联合类型,全程不会破坏原有类型的强约束。
场景1:key由内部逻辑生成
如果key是函数内部逻辑判断生成的,直接给key标注keyof 目标类型即可,TS会自动校验你赋值给key的值是不是合法属性名:
interface DataDef { name: string; floorCount: number; address: string; } function foo(data: DataDef) { // 约束key只能是DataDef的三个合法属性名之一 let key: keyof DataDef; if (Math.random() > 0.5) { key = 'name'; } else if (Math.random() > 0.3) { key = 'floorCount'; } else { key = 'address'; } // 此处无类型错误,prop会被自动推导为string | number类型 const prop = data[key]; }
如果你的分支逻辑返回的key本身就是字面量值,TS甚至不需要你手动标注类型,会自动推导出对应的联合类型,只要这个联合类型是keyof DataDef的子集,就不会报错:
function foo(data: DataDef) { // key自动推导为 'name' | 'address',属于合法键的子集 const key = Math.random() > 0.5 ? 'name' : 'address'; const prop = data[key]; // 正常通过校验 }
场景2:key来自外部不可信输入
如果key是从接口返回、用户输入等渠道拿到的普通string类型,不要直接做类型断言,加一层运行时类型守卫做校验,同时完成类型收窄:
// 定义运行时校验用的合法属性列表,as const是为了推导出精确的字面量联合类型 const LEGAL_DATA_KEYS = ['name', 'floorCount', 'address'] as const; type LegalDataKey = typeof LEGAL_DATA_KEYS[number]; // 类型守卫:判断输入的字符串是不是合法属性名 function isLegalDataKey(key: string): key is LegalDataKey { return (LEGAL_DATA_KEYS as readonly string[]).includes(key); } function foo(data: DataDef, inputKey: string) { // 非法key直接抛错或者做兜底处理 if (!isLegalDataKey(inputKey)) { throw new Error(`非法属性访问: ${inputKey}`); } // 经过守卫判断后,inputKey被收窄为LegalDataKey类型(也就是keyof DataDef) const prop = data[inputKey]; // 无类型错误 }
为什么不推荐宽泛索引签名
给类型加[key: string]: string | number这类签名后,TS会默认任意字符串都可以作为该类型的属性键:
- 你可以访问任意拼写错误的属性(比如
data.nme)而不会得到报错提示 - 你可以给对象随意新增任意字符串属性,TS不会做任何校验
相当于直接关闭了这个类型的属性名检查,完全违背了TS强类型的初衷。
内容的提问来源于stack exchange,提问作者Shan Robertson
相关产品推荐
相关产品推荐

