为何`key in object`无法通过TypeScript的类型校验?
key in object无法通过TypeScript的类型校验? 先看你给出的代码示例:
type LanguageKey = | 'c++' | 'python' | 'javascript' | 'typescript'; class Test { languages: Partial<Record<LanguageKey, string>> = {}; needsString(str: string): void { console.log(str);} useLangKey(langKey: LanguageKey): void { if (langKey in this.languages) { const language = this.languages[langKey]; this.needsString(language); // <-- TS报错:'string | undefined' 不能赋值给 'string' } } }
你疑惑的点是:既然已经用langKey in this.languages检查了键存在,为什么TypeScript还认为值可能是undefined?
原因其实很简单:
TypeScript的in操作符只能帮你确认对象里有这个键,但没法保证这个键对应的值不是undefined。
你定义的languages类型是Partial<Record<LanguageKey, string>>,它等价于{ [P in LanguageKey]?: string }——也就是每个语言键都是可选属性。在TypeScript中,可选属性的类型会自动包含undefined:
- 你可以完全不设置这个键(此时
in检查为false) - 你也可以手动给这个键赋值为
undefined(此时in检查为true,但值是undefined)
比如,你完全可以写:
this.languages['python'] = undefined;
这时候'python' in this.languages是true,但值确实是undefined,TypeScript必须考虑这种情况,所以它不会把值的类型从string | undefined缩窄到string。
怎么解决这个问题?
这里有几个常用的方案:
1. 同时检查值是否不为undefined
把条件改成既检查键存在,又检查值不是undefined:
useLangKey(langKey: LanguageKey): void { const language = this.languages[langKey]; if (language !== undefined) { this.needsString(language); // 没问题! } }
这种方式最稳妥,直接跳过值为undefined的情况。
2. 用非空断言(谨慎使用)
如果你能确保不会手动给languages的键赋值为undefined,只是用它来存储已有的语言字符串,可以用非空断言!告诉TypeScript“这个值肯定不是undefined”:
useLangKey(langKey: LanguageKey): void { if (langKey in this.languages) { const language = this.languages[langKey]; this.needsString(language!); // 用!断言非空 } }
注意:非空断言是你在向TypeScript保证值不会是undefined,如果实际运行中出现undefined,会抛出运行时错误,所以要确保逻辑安全再用。
3. 改用Map类型(推荐类型安全场景)
如果想让TypeScript自动通过存在性检查缩窄类型,可以把languages改成Map<LanguageKey, string>:
class Test { languages = new Map<LanguageKey, string>(); needsString(str: string): void { console.log(str);} useLangKey(langKey: LanguageKey): void { if (this.languages.has(langKey)) { const language = this.languages.get(langKey); this.needsString(language); // 自动缩窄为string,无报错! } } }
Map的has()方法在类型系统中是被特殊处理的:当has()返回true时,get()的返回值会自动从string | undefined缩窄为string,完美解决你的问题。
备注:内容来源于stack exchange,提问作者Ofek Shilon

