You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 18:13:12