Typescript中if(x in obj)类型守卫无法缩小自定义Record类型范围
问题根因
当前TypeScript的in运算符类型守卫仅支持收窄被判断对象的类型,不会自动将字符串类型的键收窄为目标对象的合法键联合类型,属于TS的设计限制,并非语法bug。
你代码中的hisAnswer为通用string类型,就算通过hisAnswer in toDo的判断,TS也不会默认将其判定为Answer类型,因此用它索引仅接受yes/no作为键的ToDo类型时,就会触发ts(7053)错误。
解决方案
以下是3种常用的处理方案,按安全性优先级排序:
- 方案1:自定义类型守卫(最推荐,可复用且类型安全)
先定义运行时的合法键列表,基于列表推导类型并实现类型守卫,避免类型定义重复维护:// 运行时可用的合法键列表,类型定义直接从列表推导 const ANSWER_LIST = ['yes', 'no'] as const; type Answer = typeof ANSWER_LIST[number]; type ToDo = Partial<Record<Answer, string>>; // 自定义类型守卫:判断输入字符串是否为合法Answer function isAnswer(str: string): str is Answer { return ANSWER_LIST.includes(str as Answer); } function doFunction(toDo: ToDo, hisAnswer: string): string | undefined { if (isAnswer(hisAnswer)) { return toDo[hisAnswer]; // 类型正常收窄,无报错 } // 补充非匹配分支的返回逻辑,原函数此处缺失会触发TS报错 return undefined; } - 方案2:临时类型断言(适合单次使用场景,需自行保证逻辑安全性)
直接在索引处给hisAnswer加类型断言,适合临时快速修复,缺点是如果运行时hisAnswer不是合法键,会存在类型安全隐患:type Answer = 'yes' | 'no'; type ToDo = Partial<Record<Answer, string>>; function doFunction(toDo: ToDo, hisAnswer: string): string | undefined { if (hisAnswer in toDo) { // 手动断言为合法键类型 return toDo[hisAnswer as Answer]; } return undefined; } - 方案3:给目标类型加索引签名(不推荐,会丢失原有类型校验能力)
直接给ToDo类型增加通用字符串索引签名,修复索引报错,但后续用任意字符串索引ToDo类型都不会触发TS校验,丢失类型安全:type Answer = 'yes' | 'no'; type ToDo = Partial<Record<Answer, string>> & {[key: string]: string | undefined};
另外注意原函数未定义if判断不成立时的返回值,会触发TS缺少返回值的报错,需要补充对应分支的返回逻辑,或者将函数返回值修改为string | undefined。
内容的提问来源于stack exchange,提问作者rostamiani
相关产品推荐
相关产品推荐

