文章目录

TypeScript条件类型中的never在过滤联合类型中的应用

发布于 2026-04-21 22:23:11 · 浏览 8 次 · 评论 0 条

TypeScript 条件类型中的 never 在过滤联合类型中的应用

TypeScript 的条件类型配合 never 类型,提供了一种极其强大的“过滤”机制。利用这一特性,可以从联合类型中精确剔除不需要的成员,或者从对象类型中提取特定属性的键。本文将详细介绍如何利用 Distributive Conditional Types(分布式条件类型)和 never 实现类型过滤。

核心原理:never 的吸收性

在 TypeScript 中,never 代表“不存在的类型”。在联合类型中,never 具有特殊的吸收性质:任何类型与 never 组成的联合类型,其结果都是该类型本身。

公式如下:
$$ A \mid \text{never} = A $$

这意味着,如果我们能让条件类型在需要剔除成员时返回 never,而在需要保留成员时返回该成员本身,最终的联合类型就会自动过滤掉 never


基础应用:从联合类型中剔除指定类型

通过分布式条件类型,我们可以构建一个基础的过滤工具。当泛型 T 是一个联合类型时,TypeScript 会将条件类型分发到联合类型的每一个成员上。

1. 定义基础过滤类型

创建一个名为 MyExclude 的类型别名,用于从 T 中剔除 U

type MyExclude<T, U> = T extends U ? never : T;

2. 验证过滤效果

定义一个包含多种类型的联合变量,并应用 MyExclude

type AllTypes = string | number | boolean | string[];

// 移除 string 类型
type Result = MyExclude<AllTypes, string>;
// 等同于:type Result = number | boolean | string[];

分析其执行过程:

  1. Tstring | number | boolean | string[]
  2. TypeScript 分发检查每个成员:
    • string extends string ? 成立 -> 返回 never
    • number extends string ? 不成立 -> 返回 number
    • boolean extends string ? 不成立 -> 返回 boolean
    • string[] extends string ? 不成立 -> 返回 string[]
  3. 最终结果组合:never | number | boolean | string[]
  4. 根据吸收性,结果简化为 number | boolean | string[]

为了更直观地理解这一过程,可以参考以下分发流程:

graph LR A["Input: string | number"] --> B["Start Distributive Check"] B --> C{Check: string extends string?} B --> D{Check: number extends string?} C -->|True| E["Return: never"] D -->|False| F["Return: number"] E --> G["Combine: never | number"] F --> G G --> H["Final Result: number"]

进阶应用:提取对象的函数属性键

在实际开发中,经常需要从一个对象中筛选出特定类型的方法名。例如,获取一个类或接口中所有函数类型的名称。

1. 定义过滤逻辑

编写一个 FunctionKeys 类型。它遍历对象 T 的所有键 K,检查 T[K] 是否为函数。如果是,保留键 K;否则,返回 never

type FunctionKeys<T> = {
  [K in keyof T]: T[K] extends Function ? K : never
}[keyof T];

注意最后的 [keyof T]。这是一个索引访问操作,它将前面映射类型的所有值取出来组成一个联合类型。

2. 创建示例接口

声明一个包含多种属性类型的 User 接口。

interface User {
  id: number;
  name: string;
  email: string;
  login: () => boolean;
  logout: () => void;
  updateProfile: (data: object) => void;
}

3. 应用类型过滤

使用 FunctionKeys 提取 User 接口中的方法名。

type UserMethods = FunctionKeys<User>;
// 结果类型为: "login" | "logout" | "updateProfile"

解析内部步骤:

  1. 映射类型遍历 id, name, email, login, logout, updateProfile
  2. 对于 id (number),number extends Function 为假,值变为 never
  3. 对于 login (Function),Function extends Function 为真,值变为 "login"
  4. 以此类推,生成中间对象结构:
    {
      id: never;
      name: never;
      email: never;
      login: "login";
      logout: "logout";
      updateProfile: "updateProfile";
    }
  5. 通过 [keyof User] 取值:never | never | never | "login" | "logout" | "updateProfile"
  6. never 被吸收,最终得到 "login" | "logout" | "updateProfile"

实战场景:过滤非空值

处理 API 响应时,通常会得到可能包含 nullundefined 的联合类型。我们可以利用 never 构建一个严格的非空类型。

1. 定义 NonNullable 类型

虽然 TypeScript 内置了 NonNullable,但我们可以手动实现它以理解其原理。

type MyNonNullable<T> = T extends null | undefined ? never : T;

2. 在代码中使用

假设有一个可能为空的变量类型,应用 MyNonNullable 进行清洗。

type ApiResponse = string | number | null | undefined;

type SafeResponse = MyNonNullable<ApiResponse>;
// 结果类型为: string | number

综合案例:构建类型安全的工具函数

结合上述知识,我们可以构建一个能够自动推断对象属性类型的工具函数,例如 pickByType,它只保留对象中特定类型的属性。

1. 定义返回值类型

设计一个 PickByType 类型,它接收对象 T 和类型 U,返回只包含类型为 U 的新对象类型。

type PickByType<T, U> = {
  [K in keyof T as T[K] extends U ? K : never]: T[K];
};

这里使用了 Key Remapping(键重映射)功能 as。如果属性值类型匹配,键名保持不变;否则,键名变为 never。在对象字面量类型中,键名为 never 的属性会被自动移除。

2. 实现工具函数

编写 JavaScript 逻辑来配合这个类型定义。

function pickByType<T, U>(obj: T, type: U): PickByType<T, U> {
  return Object.fromEntries(
    Object.entries(obj).filter(([_, value]) => {
      return typeof value === typeof type;
    })
  ) as PickByType<T, U>;
}

3. 验证效果

准备一个混合数据的对象,并调用该函数。

const config = {
  id: 1,
  port: 8080,
  host: "localhost",
  debug: true,
  connect: () => console.log("connecting"),
};

// 只提取数字类型的属性
const numericConfig = pickByType(config, 0);

// numericConfig 的类型被推断为:
// {
//   id: number;
//   port: number;
// }

// 以下代码会报错,因为 host 和 debug 已经被类型系统过滤掉了
console.log(numericConfig.host);
console.log(numericConfig.debug);

通过将条件不匹配时的键映射为 never,TypeScript 在构建最终对象类型时自动丢弃了这些键,从而实现了精确的类型收窄。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文