TypeScript中经undefined检查后两种类型为何仍无法统一?
问题背景
我写了这段TypeScript代码:
export type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] }; type A = { f1?: string; }; function test(a: A): WithRequired<A, 'f1'> { if (a.f1) { return a; } else { throw Error(); } }
但编译器报了这个错误:
Type 'A' is not assignable to type 'WithRequired<A, "f1">'. Type 'A' is not assignable to type '{ f1: string; }'. Types of property 'f1' are incompatible. Type 'string | undefined' is not assignable to type 'string'. Type 'undefined' is not assignable to type 'string'.
我本来觉得这段代码能正常运行,因为在return a的分支里,编译器应该能判断出a.f1不是undefined,而且TypeScript在有些场景下确实能处理这种情况。我有两个问题:
- 为什么这段代码跑不起来?(想更懂编译器的逻辑)
- 有没有办法不用类型断言实现类似功能?
问题解答
1. 代码无法运行的原因
TypeScript的类型收窄逻辑是针对变量属性的局部类型,不会自动更新整个对象的类型。
你定义的a类型是A(即{ f1?: string }),当你通过if (a.f1)判断后,编译器只会把a.f1的类型收窄为string,但整个a的类型依然是A,没有被升级为WithRequired<A, 'f1'>。而函数返回值要求的是f1必选的类型,所以A类型的a无法匹配返回类型,最终报错。
简单来说:TypeScript不会因为某个属性被验证为非空,就自动修改整个对象的类型定义。
2. 不使用类型断言的实现方案
有两种常见的可行方式:
方案一:解构属性后重新构造对象
通过解构提取属性,再和原对象合并,让编译器自动推导正确的返回类型:
export type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] }; type A = { f1?: string; }; function test(a: A): WithRequired<A, 'f1'> { const { f1 } = a; if (f1) { // 合并后新对象的f1类型是string,符合返回值要求 return { ...a, f1 }; } else { throw Error(); } }
方案二:自定义类型守卫函数
通过类型守卫明确标记对象的类型,让编译器识别验证后的对象类型:
export type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] }; type A = { f1?: string; }; // 自定义类型守卫,告诉编译器验证通过后对象的类型 function hasRequiredF1(obj: A): obj is WithRequired<A, 'f1'> { return typeof obj.f1 === 'string'; } function test(a: A): WithRequired<A, 'f1'> { if (hasRequiredF1(a)) { return a; } else { throw Error(); } }
内容的提问来源于stack exchange,提问作者A Question Asker
相关产品推荐
相关产品推荐

