如何声明指定键映射值、其余键为undefined的record type并实现类型安全索引
TypeScript中用任意字符串索引const断言对象的类型安全实现
问题场景
以下代码中,用任意字符串anyString索引as const声明的对象时,TypeScript会抛出类型错误:
const myObj = { "hello": "world" } as const; const anyString: string = "Hi" if (myObj[anyString]) { // 错误提示:Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ readonly hello: "world"; }'. console.log('Property exists!', anyString) }
用户预期myObj的类型应该隐含“仅已知键对应有效值,其他字符串键对应undefined”的逻辑,类似:
interface TMyObj { [specificKeys: "hello"]: "world", [otherKeys: string]: undefined }
但TypeScript不认可这种定义——常规字符串索引签名会允许给对象添加任意属性,可实际上as const声明的对象是只读的,添加属性本身就会报错:
myObj.bar = "other value" // 错误提示:Property 'bar' does not exist on type '{ readonly hello: "world"; }'
需求:无需强制转换anyString的类型,编写类型安全的代码,适配anyString不一定是对象键的场景。
可行方案
1. 自定义类型守卫函数
通过类型守卫在运行时检查字符串是否为对象的键,同时让TypeScript自动推断分支类型:
const myObj = { "hello": "world" } as const; type MyObjKeys = keyof typeof myObj; function isMyObjKey(key: string): key is MyObjKeys { return key in myObj; } const anyString: string = "Hi" if (isMyObjKey(anyString)) { // 此处anyString被推断为MyObjKeys类型,索引myObj无类型错误 console.log('Property exists!', anyString, myObj[anyString]); } else { // 此处可处理属性不存在的逻辑,运行时myObj[anyString]确实为undefined console.log('Property does not exist!', anyString); }
这种方案完全类型安全,无需强制转换,还能清晰区分属性存在/不存在的分支逻辑。
2. 扩展对象类型,添加只读字符串索引
通过工具类型给原对象类型添加string索引返回undefined的定义,同时保留原有属性的只读特性:
type ReadonlyObjWithStringIndex<T> = { readonly [K in keyof T]: T[K]; } & { readonly [key: string]: undefined; }; const myObj: ReadonlyObjWithStringIndex<typeof myObj> = { "hello": "world" } as const; const anyString: string = "Hi" if (myObj[anyString]) { // 此时类型为"world" | undefined,无类型错误 console.log('Property exists!', anyString); }
该方案直接修正了对象的类型定义,让TypeScript明确:任意字符串索引要么返回对应属性值,要么返回undefined。同时因为交叉了只读的字符串索引,依然无法给对象添加新属性:
myObj.bar = "other value" // 错误提示:Index signature in type 'ReadonlyObjWithStringIndex<{ readonly hello: "world"; }>' only permits reading.
3. 结合Object.hasOwn与类型断言
如果不想编写类型守卫,可先用Object.hasOwn做运行时检查,再在分支内使用类型断言:
const myObj = { "hello": "world" } as const; const anyString: string = "Hi" if (Object.hasOwn(myObj, anyString)) { // 运行时已确认属性存在,类型断言安全 console.log('Property exists!', anyString, myObj[anyString as keyof typeof myObj]); }
这种方案写法简洁,虽然用到类型断言,但有运行时检查兜底,依然保持类型安全。
内容的提问来源于stack exchange,提问作者Slava Knyazev
相关产品推荐
相关产品推荐

