条件类型与infer如果说泛型让 TypeScript 拥有了“参数化类型”的能力那么条件类型则让它开始真正具备“按条件计算类型”的能力。到了这一篇你已经进入 TypeScript 的高级区但这不意味着内容会只是炫技。恰恰相反条件类型和infer真正有价值的地方是它们能把很多原本只能靠重复、重载或手工维护的类型关系自动化。它们解决的核心问题可以概括成一句话当输入类型不同结果类型也应该随条件变化时如何把这种变化规则写出来。条件类型的基本形式typeIsStringTTextendsstring?true:false;这段写法看起来像 JavaScript 的三元表达式因为它本质上就是类型系统里的条件判断如果T可以赋值给string那结果就是true否则结果就是false它的意义不在于这个例子本身而在于你要开始接受一个事实类型系统也可以“计算”。条件类型为什么重要因为真实项目里大量类型关系都不是静态的。比如如果传入函数类型就取它的返回值类型如果传入 Promise就取其 resolve 后的值类型如果一个对象里有某个字段就提取那个字段类型如果一个联合类型里的成员满足条件就只保留那些成员这些都不是单纯的“写一个接口”能解决的问题。它们需要类型系统自己根据输入做推导。一个更实用的例子提取对象某字段类型typeMessageOfTTextends{message:unknown}?T[message]:never;这段类型表示如果T里存在message字段那就提取message的类型否则返回never例如typeAMessageOf{message:string};typeBMessageOf{id:number};结果分别是A为stringB为never这类模式在处理复杂响应结构、事件对象、第三方类型封装时非常常见。分布式条件类型是理解高级类型的关键拐点这通常是初学者第一次真正“懵”的地方。看一个经典例子typeToArrayTTextendsany?T[]:never;typeResultToArraystring|number;很多人第一反应会以为Result是(string | number)[]但实际结果是string[]|number[]为什么因为条件类型在遇到联合类型时默认会对联合中的每个成员分别计算然后再把结果联合起来。这个行为就叫分布式条件类型。你可以把它理解成ToArraystring|ToArraynumber也就是string[]|number[]这不是一个冷知识而是理解很多工具类型源码的前提。如何阻止分布式行为有时候你不想让条件类型分发而是想整体判断。这时可以把类型包在元组里typeToArrayNonDistT[T]extends[any]?T[]:never;这时typeResultToArrayNonDiststring|number;结果会更接近(string|number)[]你不一定现在就要熟练使用这个技巧但至少要知道分布式是默认行为不是唯一行为。infer是什么如果条件类型解决的是“按条件选结果”那么infer解决的就是“从一个已有类型结构里把某部分类型信息提取出来”。最经典的例子是提取函数返回值typeMyReturnTypeTTextends(...args:any[])inferR?R:never;这里的infer R可以理解成如果T是一个函数类型那请你把它的返回值类型推导出来并把这个结果临时命名为R于是typeAMyReturnType()string;结果就是string。infer的本质是在匹配过程中“拆结构”这层理解非常重要。infer不是某种特殊魔法它就是一种模式匹配式的提取能力。再看一个例子提取函数第一个参数类型typeFirstArgTTextends(arg:inferA,...rest:any[])any?A:never;又比如提取数组元素类型typeElementTypeTTextends(inferU)[]?U:never;提取 Promise resolve 后的值typePromiseValueTTextendsPromiseinferU?U:T;你会发现很多高级类型操作本质上都在做同一件事把一个大结构拆开拿出其中最关心的那一部分。为什么infer在库源码里出现频率很高因为库设计里经常需要“从用户传入的类型中自动推导信息”。例如从函数中提取参数和返回值从 Promise 中提取异步结果从 React 组件中提取 props从事件处理函数中提取事件对象类型如果没有infer很多这类能力就只能靠手写重载或人工指定体验会差很多。一个更接近工程使用的例子假设你有一个请求函数返回 PromiseasyncfunctionfetchUser(){return{id:1,name:Alice};}现在你想写一个工具类型拿到这个函数最终解析后的数据结构typeAsyncReturnTTextends(...args:any[])PromiseinferR?R:never;typeUserAsyncReturntypeoffetchUser;现在User就会自动变成{id:number;name:string;}这类写法在接口层、数据层、Hooks 封装里都非常实用。条件类型不只是“提取”也能做“筛选”例如typeOnlyStringTTextendsstring?T:never;typeResultOnlyStringstring|number|boolean;最终Result会是string这说明条件类型不仅能做分支判断还能对联合类型做筛选。TypeScript 内置的一些工具类型比如Exclude、Extract本质上就建立在这个思路上。为什么很多人会在这一篇开始走偏因为条件类型和infer确实很强也很容易让人产生一种“我终于接触到真正高级内容了”的兴奋感。问题是一旦把这种能力当成表演工具就很容易进入类型体操的陷阱。现实中的标准应该始终是这段类型有没有减少重复有没有让调用方获得更准确提示有没有让业务约束表达得更清楚后续维护的人能不能读懂如果答案都是否定那这段高级类型大概率只是炫技。一个更成熟的学习态度你不需要在刚学完这一篇时就能手写一堆复杂类型工具。更现实的目标是看懂基本条件类型语法理解分布式行为会用infer提取常见结构读懂常见内置工具类型的定义思路这已经足够让你迈入高级 TypeScript 的门槛。本文小结条件类型让 TypeScript 具备了“根据输入条件计算结果类型”的能力infer则让它能够在复杂类型结构中提取关键信息。两者结合之后类型系统就不再只是静态标签而开始具备真正的推导、拆解和派生能力。很多你以前觉得“库怎么可能自动知道这个类型”的地方背后往往就是这些机制在工作。你不一定需要天天手写它们但你必须理解它们这样你才真正开始读得懂那些高质量的 TypeScript 代码。练习写一个类型提取数组元素类型并分别测试string[]、number[]和对象数组。写一个类型提取 Promise 最终解析出的值类型再比较它和Awaited的语义。阅读一次 TypeScript 内置的ReturnType定义尝试自己解释其中infer的作用。给初学者讲解AsyncReturnT类型这是 TypeScript 中一个高级类型工具作用是提取一个异步函数的返回值类型。我们一步步拆解。一、它能做什么先看效果asyncfunctiongetUser(){return{name:张三,age:18};}typeUserAsyncReturntypeofgetUser;// User { name: string; age: number }getUser返回的是Promise{name, age}而AsyncReturn帮我们剥掉 Promise 外壳拿到里面的{name, age}。二、完整语法拆解typeAsyncReturnTTextends(...args:any[])PromiseinferR?R:never;// ① ② ③ ④ ⑤ ⑥ ⑦①type AsyncReturnT— 定义一个泛型类型别名类似函数function f(T)只不过这里处理的是类型不是值。type 定义类型别名T 接收一个类型参数②— 表示「等于」AsyncReturnT的结果就是右边算出来的类型。③T extends ... ? ... : ...— 条件类型三元表达式的类型版语法结构AextendsB?X:Y意思是如果 A 能赋值给 BA 是 B 的子类型结果就是 X否则是 Y。就像 JS 的condition ? a : b只不过判断的是「类型是否匹配」。④(...args: any[]) Promise...— 一个「函数类型模板」这是在描述「一个接收任意参数返回Promise的函数」。我们在问 TST 是不是这种函数...args: any[] 任意数量、任意类型的参数 Promise... 返回值是 Promise⑤infer R—关键类型推断infer的意思是「推断并捕获」。PromiseinferR读作「Promise 里面的那个类型我把它抓出来命名为R」。类比正则表达式的捕获组PromiseUser.match(/Promise(.)/)// ↑ 捕获组相当于 infer⑥? R— 如果匹配成功返回捕获到的R匹配成功 T 确实是一个返回 Promise 的函数 → 返回 Promise 里面的类型。⑦: never— 如果不匹配返回nevernever表示「不可能的类型」相当于「这种情况不存在/不支持」。三、整体翻译成人话typeAsyncReturnTTextends(...args:any[])PromiseinferR?R:never;如果T是一个返回Promise的函数那么把这个 Promise 里面包着的类型抓出来叫R结果就是R否则结果是never。四、实际例子对比// 例 1异步函数 ✅asyncfunctionfn1(){return123;}typeR1AsyncReturntypeoffn1;// number// 例 2返回 Promise 的普通函数 ✅functionfn2():Promisestring{returnPromise.resolve(hi);}typeR2AsyncReturntypeoffn2;// string// 例 3普通同步函数 ❌functionfn3(){return123;}typeR3AsyncReturntypeoffn3;// never因为没返回 Promise// 例 4根本不是函数 ❌typeR4AsyncReturnnumber;// never五、记忆口诀关键词作用类比extends类型判断if/? :条件分支三元运算符infer X抓出某个位置的类型并命名正则捕获组(...)never不可能的类型兜底 / 失败六、和内置类型的关系其实 TS 内置了一个类似的工具叫AwaitedTtypeUserAwaitedReturnTypetypeofgetUser;ReturnTypeT拿到函数返回值类型PromiseUserAwaitedT拿到 Promise 解析后的类型User而AsyncReturnT相当于把这两步合并了。核心要点一句话infer就是在类型层面做「模式匹配 捕获」配合extends ? :实现「如果类型长这样就把某部分抓出来用」。后记2026年5月22日于上海。
【Typescript】10-条件类型与-infer
条件类型与infer如果说泛型让 TypeScript 拥有了“参数化类型”的能力那么条件类型则让它开始真正具备“按条件计算类型”的能力。到了这一篇你已经进入 TypeScript 的高级区但这不意味着内容会只是炫技。恰恰相反条件类型和infer真正有价值的地方是它们能把很多原本只能靠重复、重载或手工维护的类型关系自动化。它们解决的核心问题可以概括成一句话当输入类型不同结果类型也应该随条件变化时如何把这种变化规则写出来。条件类型的基本形式typeIsStringTTextendsstring?true:false;这段写法看起来像 JavaScript 的三元表达式因为它本质上就是类型系统里的条件判断如果T可以赋值给string那结果就是true否则结果就是false它的意义不在于这个例子本身而在于你要开始接受一个事实类型系统也可以“计算”。条件类型为什么重要因为真实项目里大量类型关系都不是静态的。比如如果传入函数类型就取它的返回值类型如果传入 Promise就取其 resolve 后的值类型如果一个对象里有某个字段就提取那个字段类型如果一个联合类型里的成员满足条件就只保留那些成员这些都不是单纯的“写一个接口”能解决的问题。它们需要类型系统自己根据输入做推导。一个更实用的例子提取对象某字段类型typeMessageOfTTextends{message:unknown}?T[message]:never;这段类型表示如果T里存在message字段那就提取message的类型否则返回never例如typeAMessageOf{message:string};typeBMessageOf{id:number};结果分别是A为stringB为never这类模式在处理复杂响应结构、事件对象、第三方类型封装时非常常见。分布式条件类型是理解高级类型的关键拐点这通常是初学者第一次真正“懵”的地方。看一个经典例子typeToArrayTTextendsany?T[]:never;typeResultToArraystring|number;很多人第一反应会以为Result是(string | number)[]但实际结果是string[]|number[]为什么因为条件类型在遇到联合类型时默认会对联合中的每个成员分别计算然后再把结果联合起来。这个行为就叫分布式条件类型。你可以把它理解成ToArraystring|ToArraynumber也就是string[]|number[]这不是一个冷知识而是理解很多工具类型源码的前提。如何阻止分布式行为有时候你不想让条件类型分发而是想整体判断。这时可以把类型包在元组里typeToArrayNonDistT[T]extends[any]?T[]:never;这时typeResultToArrayNonDiststring|number;结果会更接近(string|number)[]你不一定现在就要熟练使用这个技巧但至少要知道分布式是默认行为不是唯一行为。infer是什么如果条件类型解决的是“按条件选结果”那么infer解决的就是“从一个已有类型结构里把某部分类型信息提取出来”。最经典的例子是提取函数返回值typeMyReturnTypeTTextends(...args:any[])inferR?R:never;这里的infer R可以理解成如果T是一个函数类型那请你把它的返回值类型推导出来并把这个结果临时命名为R于是typeAMyReturnType()string;结果就是string。infer的本质是在匹配过程中“拆结构”这层理解非常重要。infer不是某种特殊魔法它就是一种模式匹配式的提取能力。再看一个例子提取函数第一个参数类型typeFirstArgTTextends(arg:inferA,...rest:any[])any?A:never;又比如提取数组元素类型typeElementTypeTTextends(inferU)[]?U:never;提取 Promise resolve 后的值typePromiseValueTTextendsPromiseinferU?U:T;你会发现很多高级类型操作本质上都在做同一件事把一个大结构拆开拿出其中最关心的那一部分。为什么infer在库源码里出现频率很高因为库设计里经常需要“从用户传入的类型中自动推导信息”。例如从函数中提取参数和返回值从 Promise 中提取异步结果从 React 组件中提取 props从事件处理函数中提取事件对象类型如果没有infer很多这类能力就只能靠手写重载或人工指定体验会差很多。一个更接近工程使用的例子假设你有一个请求函数返回 PromiseasyncfunctionfetchUser(){return{id:1,name:Alice};}现在你想写一个工具类型拿到这个函数最终解析后的数据结构typeAsyncReturnTTextends(...args:any[])PromiseinferR?R:never;typeUserAsyncReturntypeoffetchUser;现在User就会自动变成{id:number;name:string;}这类写法在接口层、数据层、Hooks 封装里都非常实用。条件类型不只是“提取”也能做“筛选”例如typeOnlyStringTTextendsstring?T:never;typeResultOnlyStringstring|number|boolean;最终Result会是string这说明条件类型不仅能做分支判断还能对联合类型做筛选。TypeScript 内置的一些工具类型比如Exclude、Extract本质上就建立在这个思路上。为什么很多人会在这一篇开始走偏因为条件类型和infer确实很强也很容易让人产生一种“我终于接触到真正高级内容了”的兴奋感。问题是一旦把这种能力当成表演工具就很容易进入类型体操的陷阱。现实中的标准应该始终是这段类型有没有减少重复有没有让调用方获得更准确提示有没有让业务约束表达得更清楚后续维护的人能不能读懂如果答案都是否定那这段高级类型大概率只是炫技。一个更成熟的学习态度你不需要在刚学完这一篇时就能手写一堆复杂类型工具。更现实的目标是看懂基本条件类型语法理解分布式行为会用infer提取常见结构读懂常见内置工具类型的定义思路这已经足够让你迈入高级 TypeScript 的门槛。本文小结条件类型让 TypeScript 具备了“根据输入条件计算结果类型”的能力infer则让它能够在复杂类型结构中提取关键信息。两者结合之后类型系统就不再只是静态标签而开始具备真正的推导、拆解和派生能力。很多你以前觉得“库怎么可能自动知道这个类型”的地方背后往往就是这些机制在工作。你不一定需要天天手写它们但你必须理解它们这样你才真正开始读得懂那些高质量的 TypeScript 代码。练习写一个类型提取数组元素类型并分别测试string[]、number[]和对象数组。写一个类型提取 Promise 最终解析出的值类型再比较它和Awaited的语义。阅读一次 TypeScript 内置的ReturnType定义尝试自己解释其中infer的作用。给初学者讲解AsyncReturnT类型这是 TypeScript 中一个高级类型工具作用是提取一个异步函数的返回值类型。我们一步步拆解。一、它能做什么先看效果asyncfunctiongetUser(){return{name:张三,age:18};}typeUserAsyncReturntypeofgetUser;// User { name: string; age: number }getUser返回的是Promise{name, age}而AsyncReturn帮我们剥掉 Promise 外壳拿到里面的{name, age}。二、完整语法拆解typeAsyncReturnTTextends(...args:any[])PromiseinferR?R:never;// ① ② ③ ④ ⑤ ⑥ ⑦①type AsyncReturnT— 定义一个泛型类型别名类似函数function f(T)只不过这里处理的是类型不是值。type 定义类型别名T 接收一个类型参数②— 表示「等于」AsyncReturnT的结果就是右边算出来的类型。③T extends ... ? ... : ...— 条件类型三元表达式的类型版语法结构AextendsB?X:Y意思是如果 A 能赋值给 BA 是 B 的子类型结果就是 X否则是 Y。就像 JS 的condition ? a : b只不过判断的是「类型是否匹配」。④(...args: any[]) Promise...— 一个「函数类型模板」这是在描述「一个接收任意参数返回Promise的函数」。我们在问 TST 是不是这种函数...args: any[] 任意数量、任意类型的参数 Promise... 返回值是 Promise⑤infer R—关键类型推断infer的意思是「推断并捕获」。PromiseinferR读作「Promise 里面的那个类型我把它抓出来命名为R」。类比正则表达式的捕获组PromiseUser.match(/Promise(.)/)// ↑ 捕获组相当于 infer⑥? R— 如果匹配成功返回捕获到的R匹配成功 T 确实是一个返回 Promise 的函数 → 返回 Promise 里面的类型。⑦: never— 如果不匹配返回nevernever表示「不可能的类型」相当于「这种情况不存在/不支持」。三、整体翻译成人话typeAsyncReturnTTextends(...args:any[])PromiseinferR?R:never;如果T是一个返回Promise的函数那么把这个 Promise 里面包着的类型抓出来叫R结果就是R否则结果是never。四、实际例子对比// 例 1异步函数 ✅asyncfunctionfn1(){return123;}typeR1AsyncReturntypeoffn1;// number// 例 2返回 Promise 的普通函数 ✅functionfn2():Promisestring{returnPromise.resolve(hi);}typeR2AsyncReturntypeoffn2;// string// 例 3普通同步函数 ❌functionfn3(){return123;}typeR3AsyncReturntypeoffn3;// never因为没返回 Promise// 例 4根本不是函数 ❌typeR4AsyncReturnnumber;// never五、记忆口诀关键词作用类比extends类型判断if/? :条件分支三元运算符infer X抓出某个位置的类型并命名正则捕获组(...)never不可能的类型兜底 / 失败六、和内置类型的关系其实 TS 内置了一个类似的工具叫AwaitedTtypeUserAwaitedReturnTypetypeofgetUser;ReturnTypeT拿到函数返回值类型PromiseUserAwaitedT拿到 Promise 解析后的类型User而AsyncReturnT相当于把这两步合并了。核心要点一句话infer就是在类型层面做「模式匹配 捕获」配合extends ? :实现「如果类型长这样就把某部分抓出来用」。后记2026年5月22日于上海。