为何TypeScript中.filter()对联合类型属性过滤时无法正确收窄类型?
TypeScript 5.5中.filter()类型收窄失效的原因与解决方案
先看两个示例:
// 示例1:类型收窄正常工作 const example1 = [{ foo: 123 }, { foo: null }] .filter(item => { // 参数类型:{ foo: number; } | { foo: null; } return item.foo != null; }); const thisIsOK = example1; // 类型:{ foo: number; }[] // 示例2:类型收窄失效 const example2 = [{ foo: 123 }, { foo: null }] .map(({ foo }) => ({ foo })) .filter(item => { // 参数类型:{ foo: number | null; } return item.foo != null; }); const notOK = example2; // 类型:{ foo: number | null; }[]
原因分析
- 示例1的原数组是元素级联合类型数组:每个元素是
{foo: number}或{foo: null}的互斥类型。TypeScript 5.5的.filter()类型收窄逻辑能识别这种结构,过滤掉foo为null的元素后,自动推断剩余元素的类型为{foo: number}。 - 示例2经过
.map()后,数组元素变成了单一类型:每个对象的foo属性是number | null的联合类型,整个数组类型是{foo: number | null}[]。此时TypeScript无法仅通过属性的非空判断,自动将整个对象的类型收窄——因为类型系统默认不会假设“属性满足条件时,整个对象的类型会被修改”,必须显式声明类型收窄规则。
解决方案
方法1:使用类型断言函数(推荐,类型安全)
定义一个类型断言函数,明确告诉TypeScript过滤后的对象类型:
function hasNonNullFoo<T extends { foo: unknown }>(item: T): item is T & { foo: NonNullable<T['foo']> } { return item.foo != null; } const example2 = [{ foo: 123 }, { foo: null }] .map(({ foo }) => ({ foo })) .filter(hasNonNullFoo); const fixed = example2; // 类型:{ foo: number; }[]
方法2:在filter回调中直接声明类型断言
不需要单独写函数,直接在回调里指定类型收窄规则:
const example2 = [{ foo: 123 }, { foo: null }] .map(({ foo }) => ({ foo })) .filter((item): item is { foo: number } => { return item.foo != null; }); const fixed = example2; // 类型:{ foo: number; }[]
方法3:使用类型断言(简单场景可用,不够安全)
如果是明确的简单场景,可以直接在过滤后断言类型:
const example2 = ([{ foo: 123 }, { foo: null }] .map(({ foo }) => ({ foo })) .filter(item => item.foo != null)) as { foo: number }[];
内容的提问来源于stack exchange,提问作者kjetilh
相关产品推荐
相关产品推荐

