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

TypeScript如何从类型中移除值为undefined/null的属性?

TypeScript 过滤类型中值为 undefined/null 的属性及请求函数实现

一、创建过滤后的类型

要移除类型中值为 undefined 或 null 的属性,可以通过映射类型+条件判断筛选出符合要求的键,再构造新类型:

方式1:Pick 筛选有效属性

// 通用工具类型:过滤掉值为 undefined 或 null 的属性
type FilterUndefinedNullProps<T> = Pick<T, {
  [K in keyof T]: T[K] extends undefined | null ? never : K
}[keyof T]>;

// 原类型
type TEndpointsPayload = {
    getList: undefined,
    open: { id: string },
    close: { id: string }
};

// 生成目标类型:自动移除 getList
type TEndpointsNonEmptyPayload = FilterUndefinedNullProps<TEndpointsPayload>;

方式2:Omit 排除无效属性

也可以先找出需要排除的键,再用 Omit 移除:

// 通用工具类型:找出值为 undefined 或 null 的属性键
type KeysWithUndefinedNull<T> = { [K in keyof T]: T[K] extends undefined | null ? K : never }[keyof T];

// 生成目标类型
type TEndpointsNonEmptyPayload = Omit<TEndpointsPayload, KeysWithUndefinedNull<TEndpointsPayload>>;

两种方式最终都会得到你期望的 TEndpointsNonEmptyPayload,属性 getList 会被彻底移除,而非设为 never。

二、请求函数的实现方案

方案1:函数重载

通过重载区分「需要传payload」和「不需要传payload」的场景,类型检查更清晰:

// 先定义过滤后的类型(复用上面的工具类型)
type FilterUndefinedNullProps<T> = Pick<T, {
  [K in keyof T]: T[K] extends undefined | null ? never : K
}[keyof T]>;

type TEndpointsPayload = {
    getList: undefined,
    open: { id: string },
    close: { id: string }
};

type TEndpointsNonEmptyPayload = FilterUndefinedNullProps<TEndpointsPayload>;
// 提取不需要传payload的接口键
type TEndpointsEmptyPayload = Exclude<keyof TEndpointsPayload, keyof TEndpointsNonEmptyPayload>;

class ApiService {
  // 重载1:需要传入payload的接口
  private request<T extends keyof TEndpointsNonEmptyPayload>(endpoint: T, payload: TEndpointsNonEmptyPayload[T]): void;
  // 重载2:不需要传入payload的接口
  private request<T extends TEndpointsEmptyPayload>(endpoint: T): void;
  
  // 实际实现逻辑
  private request(endpoint: keyof TEndpointsPayload, payload?: unknown): void {
    // 这里编写请求逻辑,比如根据endpoint处理payload
    if (payload) {
      console.log(`请求 ${endpoint},参数:`, payload);
    } else {
      console.log(`请求 ${endpoint}`);
    }
  }

  // 测试调用(类型检查生效)
  test() {
    this.request('getList'); // ✅ 正确,无需传参
    this.request('open', { id: 'user-123' }); // ✅ 正确,必须传参
    // this.request('open'); // ❌ 报错:缺少payload参数
    // this.request('getList', {}); // ❌ 报错:多余的参数
  }
}

方案2:条件类型+剩余参数

无需额外定义多个类型,直接在函数参数中用条件类型动态判断是否需要传入payload:

type TEndpointsPayload = {
    getList: undefined,
    open: { id: string },
    close: { id: string }
};

class ApiService {
  private request<T extends keyof TEndpointsPayload>(
    endpoint: T,
    // 条件判断:如果属性值是undefined/null,则不需要传参;否则必须传对应类型的payload
    ...args: TEndpointsPayload[T] extends undefined | null ? [] : [TEndpointsPayload[T]]
  ): void {
    const payload = args[0];
    console.log(`请求 ${endpoint}`, payload);
  }

  // 测试调用
  test() {
    this.request('getList'); // ✅ 正确
    this.request('open', { id: 'user-456' }); // ✅ 正确
    // this.request('open'); // ❌ 报错:缺少必要参数
    // this.request('getList', {}); // ❌ 报错:多余参数
  }
}

三、为什么直接用 Omit/Exclude 没生效?

你之前尝试的 Omit/Exclude 没成功,是因为没有正确筛选出需要排除的键。直接使用 Exclude<keyof TEndpointsPayload, ...> 时,需要先明确哪些键对应的值是 undefined/null——上面的工具类型就是帮你完成这一步:先通过映射类型标记出无效键为 never,再提取这些键进行排除,才能彻底移除属性。

内容的提问来源于stack exchange,提问作者Itay Elkouby

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 19:10:38