news 2026/9/26 2:41:33

The Concise TypeScript Book:深入掌握 8 个预定义条件类型(Predefined Conditional Types)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
The Concise TypeScript Book:深入掌握 8 个预定义条件类型(Predefined Conditional Types)
  • 文档
  • 教程

【免费下载链接】typescript-book

The 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 类型系统中"根据输入类型特征做变换"的核心机制,而预定义条件类型则是语言内置的、开箱即用的条件类型工具集合。本文以 The Concise TypeScript Book 的对应章节为主体骨架,系统讲解Exclude、Extract、NonNullable、ReturnType、Parameters、Required、Partial、Readonly共 8 个内置条件类型:先厘清它们各自的语义与底层实现原理,再结合仓库中的类型系统系列章节给出可复制的实战示例与组合用法。读完本文,你将能够熟练使用这些工具类型在编译期完成联合类型过滤、函数签名提取、对象属性修饰符改写等常见类型变换。

一、先理解基础:条件类型从何而来

预定义条件类型并非孤立概念,它们全部建立在 TypeScript 条件类型语法之上。正如仓库中 conditional-types.md 所定义的:条件类型是一种根据条件结果来决定最终类型的机制,使用extends关键字加三元运算符在两种类型间做选择:

type IsArray<T> = T extends any[] ? true : false; const myArray = [1, 2, 3]; const myNumber = 42; type IsMyArrayAnArray = IsArray<typeof myArray>; // Type true type IsMyNumberAnArray = IsArray<typeof myNumber>; // Type false

这里T extends any[] ? true : false的含义是:如果T是any[]的子类型,则结果为true,否则为false。可以把extends理解为"子集"关系(T1 ⊆ T2),这与 exploring-the-type-system.md 中"Types as Sets(类型即集合)"的视角一致。

条件类型有两个关键特性,它们是预定义条件类型行为的基石:

  • 分配律(Distributive):当T是联合类型时,条件类型会对每个联合成员分别求值再合并结果,详见 distributive-conditional-types.md:
type Nullable<T> = T extends any ? T | null : never; type NumberOrBool = number | boolean; type NullableNumberOrBool = Nullable<NumberOrBool>; // number | boolean | null
  • infer类型推断:条件类型分支中可用infer关键字提取泛型参数的具体类型,详见 infer-type-inference-in-conditional-types.md:
type ElementType<T> = T extends (infer U)[] ? U : never; type Numbers = ElementType<number[]>; // number type Strings = ElementType<string[]>; // string

正是"分配律 +infer"这两个能力,构成了Exclude、Extract、ReturnType、Parameters等预定义条件类型的实现底座。

二、联合类型过滤器:Exclude 与 Extract

Exclude与Extract是作用在联合类型上的"正反"两个过滤器,它们的语义在原文档中定义如下:

  • Exclude<UnionType, ExcludedType>:从UnionType中删除所有可赋值给ExcludedType的类型。
  • Extract<Type, Union>:从Union中提取所有可赋值给Type的类型。

先看一个对照示例:

type Union = 'a' | 'b' | 'c'; type Excluded = Exclude<Union, 'a' | 'c'>; // "b" type Extracted = Extract<Union, 'a' | 'c'>; // "a" | "c"

如果把联合类型看作集合,Exclude<T, U>等价于集合差集 T − U,Extract<T, U>等价于集合交集 T ∩ U。它们的典型应用场景包括:

  • 剔除事件类型中的某些分支;
  • 从对象键集合中排除特定键:
type Keys = 'name' | 'age' | 'email'; type PublicKeys = Exclude<Keys, 'email'>; // "name" | "age" type Falsy = false | 0 | '' | null | undefined; type Truthy = Exclude<any, Falsy>; // 过滤掉所有假值类型

Extract的一个实用场景是从接口键中只挑出字符串键或数字键:

interface Article { title: string; views: number; [key: string]: unknown; } type StringKeys = Extract<keyof Article, string>; // string(包含索引签名)

仓库中 type-manipulation.md 的 Utility Types 小节同样收录了Exclude与Extract的用法,并给出了与'a' | 'b' | 'c'联合的相同示例,可作为交叉印证。

三、清理空值:NonNullable

NonNullable<Type>的语义非常直接:从Type中移除null和undefined。

type Union = 'a' | null | undefined | 'b'; type MyType = NonNullable<Union>; // 'a' | 'b'

该类型最常见的价值在于收窄从 API、表单或第三方库传入的"可能为空"的数据类型。例如 React 中常见的 state 初始化写法:

type User = { id: number; name: string }; // 初始态可能是 null,但后续逻辑保证非空 const initial: User | null = null; type UserState = NonNullable<typeof initial>; // { id: number; name: string }

在实际项目中,NonNullable常与可选链、判空分支搭配,让类型层面与运行时空值防御形成"双重保险"。从实现机理看,NonNullable<T>等价于条件类型T extends null | undefined ? never : T:由于条件类型具备分配律,联合类型中的每个成员都会被单独检查,凡是可赋值给null | undefined的成员一律变为never,最终从联合中消失——这也解释了为什么它一次能同时剔除两种空值。

四、提取函数签名:ReturnType 与 Parameters

ReturnType与Parameters是infer关键字在预定义条件类型中最典型的两处应用:

  • ReturnType<Type>:提取函数Type的返回值类型。
  • Parameters<Type>:提取函数Type的参数类型列表。
type Func = (name: string) => number; type MyType = ReturnType<Func>; // number
type Func = (a: string, b: number) => void; type MyType = Parameters<Func>; // [a: string, b: number]

注意Parameters的返回结果是元组类型(tuple),保留了参数顺序与可选性标记(如[a: string, b?: number])。这在装饰器、高阶函数和事件处理器的类型推导中非常实用:

function makeRequest(url: string, options?: { timeout: number }) {} type RequestParams = Parameters<typeof makeRequest>; // [url: string, options?: { timeout: number } | undefined] type RequestResult = ReturnType<typeof makeRequest>; // void

一个真实感很强的组合场景:封装一个包装函数,把被包装函数的参数与返回值全部原样透传:

type Logger<T extends (...args: any[]) => any> = ( ...args: Parameters<T> ) => ReturnType<T>; const add = (a: number, b: number): number => a + b; const loggedAdd: Logger<typeof add> = (a, b) => { console.log(`adding ${a} + ${b}`); return a + b; // 返回值类型被保证为 number };

它们的底层原理正是条件类型加infer:ReturnType<T>在分支中通过T extends (...args: any[]) => infer R ? R : any提取R,Parameters<T>则通过T extends (...args: infer P) => any ? P : never提取参数元组P。这与 infer-type-inference-in-conditional-types.md 中ElementType的写法一脉相承。

五、改写对象属性:Required、Partial 与 Readonly

最后三个预定义条件类型作用于对象的属性修饰符,本质上是映射类型(Mapped Types)的语法糖:

  • Required<Type>:将Type的所有属性变为必填。
  • Partial<Type>:将Type的所有属性变为可选。
  • Readonly<Type>:将Type的所有属性变为只读。

仓库中 mapped-types.md 解释了映射类型的基本形态——用[P in keyof T]遍历对象键并逐个变换属性;mapped-type-modifiers.md 则给出了三个修饰符的底层写法,与这三个工具类型的实现完全对应:

type ReadOnly<T> = { readonly [P in keyof T]: T[P] }; // 等价于 Readonly<T> type Mutable<T> = { -readonly [P in keyof T]: T[P] }; // 去除 readonly type MyPartial<T> = { [P in keyof T]?: T[P] }; // 等价于 Partial<T>

5.1 Required:全部属性转必填

type Person = { name?: string; age?: number; }; type A = Required<Person>; // { name: string; age: number; }

典型应用是:当配置对象在"初始化阶段"允许缺省、但在"使用阶段"必须完整时,用Required约束后者:

type AppConfig = { host?: string; port?: number; }; // 经过默认值合并后的配置必须是完整的 function startServer(config: Required<AppConfig>) { console.log(`${config.host}:${config.port}`); }

Required<T>的底层实现可理解为对每个属性键应用-?修饰符:{ [P in keyof T]-?: T[P] },即去除可选项标记。

5.2 Partial:全部属性转可选

type Person = { name: string; age: number; }; type A = Partial<Person>; // { name?: string | undefined; age?: number | undefined; }

Partial最常见的用法是"部分更新"场景——函数的入参只需提供要修改的字段:

type User = { id: number; name: string; email: string; }; function updateUser(id: number, patch: Partial<User>) { // 调用方只需传入需要变更的字段 } updateUser(1, { name: 'new name' }); // 合法 updateUser(1, { id: 2, email: 'a@b.c' }); // 合法

注意Partial<T>产生的结果中,可选属性会附带| undefined,因此直接读取可能得到undefined,需要配合判空或默认值处理。

5.3 Readonly:全部属性转只读

type Person = { name: string; age: number; }; type A = Readonly<Person>; const a: A = { name: 'Simon', age: 17 }; a.name = 'John'; // Invalid:只读属性不可赋值

Readonly非常适合表达"创建后不可变更"的领域对象或常量配置。需要说明的是,Readonly<T>是浅只读:它只锁定第一层属性,嵌套对象的内部属性仍可修改;对于深层只读,需要递归组合Readonly与索引访问类型,例如type DeepReadonly<T> = { readonly [P in keyof T]: DeepReadonly<T[P]> }。

三个工具类型可以自由嵌套组合,形成更强的类型约束:

type Point = { x?: number; y?: number }; // 先补全必填,再锁定只读 type ImmutablePoint = Readonly<Required<Point>>; // { readonly x: number; readonly y: number; }

六、进阶组合与实战技巧

6.1 用 keyof 与 Exclude 动态生成键集合

把keyof(取对象所有键的联合类型)与Exclude/Extract组合,可以在不手写字符串的前提下按条件筛选键:

type FormField = { label: string; value: unknown; hidden?: boolean }; // 剔除可选键,得到必填字段的键集合 type RequiredKeys<T> = Exclude<keyof T, keyof Required<Pick<T, keyof T>>>; type FormRequiredKeys = RequiredKeys<FormField>; // "label" | "value"(hidden 是可选)

说明:RequiredKeys这类高阶工具并非语言内置,而是社区中常见的组合技法;其可行性正建立在Exclude与Required两个内置类型的语义之上。

6.2 函数工具类型的生产级用法

ReturnType与Parameters在依赖注入、事件总线和插件系统里是高频工具。一个经典模式是"从现有函数类型重建一个签名相同的兼容函数":

type Handler = (event: MouseEvent) => void; // 为 handler 增加日志而保持签名完全一致 const safeHandler: Handler = (event) => { console.log('handling', event.type); };

如果第三方库没有导出某个函数的参数/返回值类型,Parameters<typeof fn>与ReturnType<typeof fn>可以帮你"凭空提取"出这些类型,无需手写重复声明。

6.3 在仓库中的其他章节定位它们

这些预定义条件类型并非孤立知识点,它们与仓库中多个章节构成完整的知识链条,可交叉阅读:

  • conditional-types.md:条件类型语法基础(extends+ 三元);
  • distributive-conditional-types.md:分配律如何让Exclude/Extract/NonNullable对联合逐成员生效;
  • infer-type-inference-in-conditional-types.md:ReturnType/Parameters的infer实现原理;
  • mapped-types.md 与 mapped-type-modifiers.md:Required/Partial/Readonly的映射底层;
  • type-manipulation.md:Utility Types 全集,包含Record、Pick、Omit、Awaited、NoInfer等更多内置工具类型的完整示例;
  • never-type.md:never作为"空集合"在条件类型分支(? ... : never)中的角色,以及它在穷尽性检查中的应用。

七、速查总表

预定义条件类型语义典型底层写法典型场景
Exclude<T, U>从 T 中剔除可赋值给 U 的成员T extends U ? never : T联合类型过滤、键集合排除
Extract<T, U>从 T 中提取可赋值给 U 的成员T extends U ? T : never求交集、按条件筛选键
NonNullable<T>从 T 中移除null与undefinedT extends null \| undefined ? never : T空值收窄、API 数据清洗
ReturnType<T>提取函数 T 的返回值类型T extends (...a: any) => infer R ? R : any函数返回值类型复用
Parameters<T>提取函数 T 的参数元组T extends (...a: infer P) => any ? P : never参数类型复用、函数包装
Required<T>所有属性转必填{ [P in keyof T]-?: T[P] }配置完整性校验
Partial<T>所有属性转可选{ [P in keyof T]?: T[P] }部分更新入参
Readonly<T>所有属性转只读{ readonly [P in keyof T]: T[P] }不可变对象建模

结语

八个预定义条件类型虽然各自解决一个具体问题,但它们的共同点在于:全部由条件类型语法(extends条件判断)、分配律和infer推断三个基础能力派生而来。掌握了它们背后的机理,你不仅能熟练使用这些内置工具,还能以此为模板写出自己的条件类型工具——这正是 TypeScript 类型系统"用类型构造类型"的威力所在。建议对照仓库中的 predefined-conditional-types.md(法文版)或 英文版原文 原文逐条动手验证,并在 IDE 中 hover 观察每个类型求值后的真实结果。

  • 文档
  • 教程

【免费下载链接】typescript-book

The Concise TypeScript Book: A Concise Guide to Effective Development in TypeScript. Free and Open Source.

项目地址:https://gitcode.com/gh_mirrors/typ/typescript-book
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 2:40:56

判据、全绿、缺口、漂移与自查 —— 线上补课(下)

系列&#xff1a;gdev-master&#xff08;NVIDIA/nouveau 用户态 GPGPU 运行时&#xff09;从 C/C 到 Rust 的移植工程 第三部换了一个问题&#xff1a;前两部回答「怎么搬」&#xff0c;这一部回答「凭什么说对了」。 一句话核心最值钱的一格每写一条检查&#xff0c;同时写…

作者头像 李华
网站建设 2026/9/26 2:40:21

深度探索:Bifrost PR 评审意见的系统化解决工作流

人工智能LLM 网关API网关后端 【免费下载链接】bifrost Fastest enterprise AI gateway (50x faster than LiteLLM) with adaptive load balancer, cluster mode, guardrails, 1000 models support & <100 s overhead at 5k RPS. 项目地址&#xff1a; https://gitcode.…

作者头像 李华
网站建设 2026/9/26 2:40:02

目前知名的IP驱动产业新场景新工具哪家专业

引言当前数字IP的价值边界已经从传统内容创作&#xff0c;延伸到实体零售、康养服务、本地生活等全产业链路&#xff0c;IP驱动产业新场景新工具成为大量经营主体轻量化转型的刚需。市面上相关解决方案繁杂&#xff0c;多数工具存在IP权属不清、场景适配性差、收益分配机制不合…

作者头像 李华
网站建设 2026/9/26 2:37:57

TensorFlow CNN水果识别毕业设计源码:从环境搭建到模型评估全流程

简介&#xff1a;这份资源是面向计算机相关专业毕业设计学生与希望提升工程能力的开发者的一套TensorFlow卷积神经网络水果图像识别项目源码&#xff0c;难度定位中等&#xff0c;适合作为课程设计、期末项目或毕业设计参考。压缩包共1058个文件&#xff0c;约79.95MB&#xff…

作者头像 李华