TypeScript 可分配条件类型(Distributive Conditional Types)深入指南
文档教程【免费下载链接】typescript-bookThe Concise TypeScript Book: A Concise Guide to Effective Development in TypeScript. Free and Open Source.项目地址https://gitcode.com/gh_mirrors/typ/typescript-book点击查看免费下载条件类型Conditional Types是 TypeScript 类型系统中最强大的工具之一而**可分配条件类型Distributive Conditional Types则是其中最容易让开发者感到魔法的特性。它允许一个类型在联合类型Union Type**上自动分发对联合的每一个成员逐一应用同一套类型转换再重组为新的联合类型。本指南基于开源项目 typescript-book 中Distributive Conditional Types一节的讲解从定义、底层机制、实战案例到与内置工具类型的关联全面剖析这一特性读完你将能熟练运用它编写可复用的高阶类型工具并理解Exclude、Extract、NonNullable等内置类型为何会表现出分发行为。什么是可分配条件类型在 TypeScript 中条件类型使用extends关键字配合三元运算符根据条件在两个类型之间二选一。本仓库的条件类型章节给出了最基础的形式type IsArrayT T extends any[] ? true : false; const myArray [1, 2, 3]; const myNumber 42; type IsMyArrayAnArray IsArraytypeof myArray; // Type true type IsMyNumberAnArray IsArraytypeof myNumber; // Type false可分配条件类型是条件类型的一个特殊行为当条件类型的裸类型参数naked type parameter即直接出现在extends左侧、没有被任何类型构造器包裹的泛型参数被传入一个联合类型时TypeScript 会把这个联合类型拆开对每个成员分别求值条件最后再把结果重新合并成联合类型。核心示例Nullable 类型原文档给出了一个最经典的可分配条件类型示例type NullableT T extends any ? T | null : never; type NumberOrBool number | boolean; type NullableNumberOrBool NullableNumberOrBool; // number | boolean | null让我们逐步拆解这个示例的执行过程NullableT是一个条件类型其泛型参数T以裸形式出现在extends any左侧因此它具备分发能力传入NullableNumberOrBool时NumberOrBool是联合类型number | boolean于是分发机制被触发类型系统分别对number和boolean求值Nullablenumber→number extends any ? number | null : never→number | nullNullableboolean→boolean extends any ? boolean | null : never→boolean | null两个结果重新合并(number | null) | (boolean | null)去重后得到number | boolean | null。也就是说NullableT把联合类型的每一个成员都转换成了该成员或 null从而为整个联合类型统一添加可空性。这正是文档中所说的对联合的每个成员单独应用一个变换。对比如果没有分发行为如果条件类型中的类型参数被某种类型构造器包裹例如T[]、PromiseT、[T]则不会发生分发条件会被整体求值。例如type Nullable2T [T] extends [any] ? T | null : never; type R Nullable2number | boolean; // (number | boolean) | null而不是 number | boolean | null注意这里结果虽然在字面上看似等价但内部结构不同——Nullable2没有逐成员分发T被视为一个整体。这一裸参数规则是理解分发行为的关键也是许多类型工具在实现时用[T] extends [U]包裹参数来主动关闭分发的原因。分发机制详解1. 裸类型参数naked type parameter只有当泛型参数以裸形式直接出现在extends左侧时才会触发分发。T extends U ? X : Y中的T就是裸参数。如果写成T[] extends U[] ? X : Y或PromiseT extends PromiseU ? X : Y则不再分发。2. 分发的结果自动去重分发对每个成员独立求值最终结果中重复的成员会被自动合并。例如type ToArrayT T extends any ? T[] : never; type R ToArraystring | number | string; // string[] | number[]重复的 string 只保留一次3. 空联合never的特殊行为当传入的联合类型是never即没有任何成员时分发无事可做结果仍然是nevertype R Nullablenever; // never这与never 类型章节中never 代表永远不会出现的值的定义一脉相承对空集逐成员变换结果仍是空集。与联合类型、never 类型的关系要真正理解可分配条件类型需要先掌握两个前置概念它们在本书中都有专门章节联合类型用|连接多个类型表示值可以是其中任意一个。分发机制的输入与输出都是联合类型。never 类型在条件类型中never常被用作分支的兜底——当条件不满足时返回never表示这个分支不产生任何类型。在NullableT中never分支保证了当T不满足条件时结果为空。两者结合使条件类型能够充当类型层面的过滤器保留满足条件的成员真分支返回成员本身丢弃不满足条件的成员假分支返回never在联合重组时自然消失。实战用分发实现类型过滤可分配条件类型最常见的实战用途是从联合类型中筛选成员。例如我们可以自己实现一个只保留函数类型的工具type ExtractFunctionT T extends (...args: any[]) any ? T : never; type Mixed string | (() void) | number | (() string); type OnlyFunctions ExtractFunctionMixed; // (() void) | (() string)执行过程分发机制对string、(() void)、number、(() string)分别求值其中函数类型满足extends条件被保留string和number不满足条件落入never分支在合并时被剔除。这个模式正是 TypeScript 内置类型Exclude与Extract的实现原理相关内容参见预定义条件类型章节ExcludeUnionType, ExcludedType从联合类型中移除所有可赋值给ExcludedType的成员ExtractType, Union从联合类型中提取所有可赋值给Union的成员NonNullableType移除null和undefined。它们都可以用裸参数条件类型 never 兜底的方式自行实现type MyExcludeT, U T extends U ? never : T; type MyExtractT, U T extends U ? T : never; type MyNonNullableT T extends null | undefined ? never : T; type A MyExcludea | b | c, a; // b | c type B MyExtracta | b | c, a | c; // a | c type C MyNonNullablea | null | undefined; // a注意MyExclude之所以能逐成员过滤正是因为T以裸参数形式出现在extends左侧——这就是分发机制的威力。与 infer 结合更高阶的类型变换可分配条件类型经常与infer关键字组合使用。infer允许在条件类型中从依赖泛型的类型里提取出某个子类型详见infer 类型推断章节type ElementTypeT T extends (infer U)[] ? U : never; type Numbers ElementTypenumber[]; // number type Strings ElementTypestring[]; // string由于T是裸类型参数ElementType也具备分发能力type Mixed ElementTypenumber[] | string[]; // number | string分发机制先对number[]提取出number再对string[]提取出string最后合并为number | string。这种逐成员提取 合并的模式在解析复杂联合类型时极为实用。与映射类型Mapped Types的协同文档指出可分配条件类型在配合映射类型mapped types或高阶类型时尤其有用。映射类型通过keyof遍历已有类型的属性并逐属性变换参见映射类型章节type MyMappedTypeT { [P in keyof T]: T[P][]; };两者的分工非常清晰映射类型针对对象类型的属性集合逐属性变换可分配条件类型针对联合类型的成员集合逐成员变换。例如先对对象类型的每个属性值取联合再做分发处理即可构建强大的组合型工具type ValuesT T[keyof T]; // 取出 T 所有属性值的联合 type NonNullableValuesT NonNullableValuesT; // 再对联合做逐成员过滤 type Config { host: string; port: number | null; debug: boolean | undefined; }; type CleanConfig NonNullableValuesConfig; // string | number | boolean常见陷阱与规避陷阱一把类型参数包裹起来导致分发失效很多开发者发现自己的条件类型没有按预期分发往往是因为把裸参数写成了T[]、[T]、PromiseT等形式。如果需要关闭分发例如想整体判断联合类型可以有意用[T] extends [U]包裹type IsUnionT [T] extends [T extends any ? T : never] ? false : true; type R1 IsUnionstring; // false type R2 IsUnionstring | number; // true陷阱二分发在嵌套条件类型中的行为当条件类型嵌套时只要每一层的裸参数仍然是裸的分发会持续发生。务必在每一层都确认自己是否期望分发行为。陷阱三never作为输入如前面所述never是空联合分发结果恒为never。这在某些递归条件类型场景中恰好是理想的终止条件。在本书中的位置与延伸阅读可分配条件类型位于本书目录的第 41 节处于条件类型体系的核心位置其上下文脉络如下条件类型第 40 节条件类型的基础语法与语义可分配条件类型第 41 节本文分发机制infer 类型推断第 42 节在条件类型中提取子类型预定义条件类型第 43 节基于以上机制的内置工具类型Exclude、Extract、NonNullable、ReturnType、Parameters等。此外类型操纵章节中从类型创建类型一节系统梳理了交集类型、联合类型、映射类型、条件类型的组合运用值得一并阅读。小结可分配条件类型是 TypeScript 类型系统以类型为数据、以变换为函数思想的集中体现。掌握它的三个要点即可游刃有余裸类型参数触发分发T extends U ? X : Y中T裸露时联合成员被逐一处理never是过滤器用never分支丢弃不满足条件的成员实现类型层面的过滤与 infer、映射类型、内置工具类型联动它们是构建高阶类型工具的基础积木。下次当你需要对联合类型中的每一个成员统一做点变换时不妨先想想这个需求能否用一个可分配条件类型优雅地解决赞分享文档教程【免费下载链接】typescript-bookThe Concise TypeScript Book: A Concise Guide to Effective Development in TypeScript. Free and Open Source.项目地址https://gitcode.com/gh_mirrors/typ/typescript-book点击查看免费下载相关推荐Hurl hurlfmt 完全指南Hurl 文件的格式化、检查与多格式转换Hurl hurlfmt 完全指南Hurl 文件的格式化、检查与多格式转换 hurlfmt 是 Hurl 项目自带的官方格式化工具用于对 Hurl 文件文档教程lo 库 MinBy 深度解析用自定义比较函数在泛型切片中查找最小值lo 库 MinBy 深度解析用自定义比较函数在泛型切片中查找最小值 导读 lo.MinBy 是 Lodash 风格 Go 泛型库 lo 中 find 子包提文档教程Beekeeper Studio 跨平台安装指南Windows、macOS 与 Linux 全系安装方式详解Beekeeper Studio 跨平台安装指南Windows、macOS 与 Linux 全系安装方式详解 Beekeeper Studio 是一款面向 M文档教程上一篇AlphaFold3-PyTorch用深度学习预测蛋白质结构的革命性工具下一篇【开源方案】微信聊天记录本地化永久保存与智能分析完整指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考