- 文档
- 教程
【免费下载链接】typescript-book
The Concise TypeScript Book: A Concise Guide to Effective Development in TypeScript. Free and Open Source.
本指南以《The Concise TypeScript Book》开源仓库中 类型谓词章节(pt-br 版) 为骨架,系统讲解 TypeScript 类型谓词(Type Predicates)的定义、语法、实战用法,以及 TypeScript 5.5 引入的自动推断谓词能力。读完本文,你将掌握如何编写自定义类型守卫(User-Defined Type Guards),理解x is T与boolean返回类型的本质区别,并能在.filter()等场景中让类型系统自动收窄结果,写出类型更精确、错误更少的代码。
什么是类型谓词
在 TypeScript 中,类型谓词(Type Predicates)是返回布尔值的函数,其特殊之处在于:它不只是告诉代码"条件成立与否",还通过特殊的返回类型语法,将传入变量的类型收窄(narrow)到更具体的类型。
类型收窄(Narrowing)是 TypeScript 在条件分支内精化变量类型的过程,在与联合类型(Union Types)配合时尤为有用。关于收窄的完整机制(typeof守卫、真值收窄、相等性收窄、in运算符、instanceof等),可参考仓库中的 收窄章节。
类型谓词的典型语法是value is Type,它由三部分组成:
- 函数必须返回
boolean类型的值(谓词判定结果); - 返回类型声明为
parameterName is TargetType,其中parameterName必须是函数的某个参数名; - 函数体内负责执行运行时检查,确认该参数是否属于
TargetType。
基础示例:自定义isString守卫
原文档给出了最简洁也最经典的入门示例:
const isString = (value: unknown): value is string => typeof value === 'string'; const foo = (bar: unknown) => { if (isString(bar)) { console.log(bar.toUpperCase()); } else { console.log('não é uma string'); // 不是字符串 } };逐行拆解这个示例:
value: unknown:参数被声明为unknown,即"未知类型"——它可能是任何值,此时 TypeScript 不允许直接调用.toUpperCase();: value is string:返回类型声明是类型谓词,表示"如果该函数返回true,那么value就是string";- 函数体
typeof value === 'string':在运行时完成真正的检查; - 在
foo中,当isString(bar)为真时,TypeScript 会在if分支内把bar从unknown收窄为string,因此bar.toUpperCase()不会报错;else分支中则保持为unknown。
这里的核心价值在于:bar在if分支内被"记住"了更精确的类型。如果没有类型谓词,即便isString返回true,bar在编译期仍然是unknown,调用bar.toUpperCase()会直接报错。
为什么返回boolean不够:类型谓词 vs 普通布尔函数
如果isString的返回类型写成boolean,代码在运行时行为完全一致,但 TypeScript 编译器不会在if分支内收窄bar的类型。区别在于:
| 返回类型 | 运行时行为 | 编译期效果 |
|---|---|---|
boolean | 返回真/假 | 不产生任何类型收窄,bar仍是unknown |
value is string | 返回真/假 | 返回true时,value被收窄为string |
这正是"谓词"一词的含义:它断言"这个值满足string这一类型条件"。这种由开发者编写辅助函数来完成类型收窄的做法,在官方语境中被称为用户自定义类型守卫(User-Defined Type Guards)。仓库的 探索类型系统章节 中有一个更贴近真实业务场景的过滤示例:当filter内联回调无法正确推断时,提取出自定义守卫即可修复类型:
const data = ['a', null, 'c', 'd', null, 'f']; const r1 = data.filter(x => x != null); // 类型仍是 (string | null)[],TypeScript 无法正确推断 const isValid = (item: string | null): item is string => item !== null; // 自定义类型守卫 const r2 = data.filter(isValid); // 类型正确收窄为 string[],谓词守卫完成了类型收窄同样的思路也适用于数组场景:用一个可复用的守卫函数(如isValid)替代内联回调,让filter的结果类型从(string | null)[]变为string[]。
TypeScript 5.5 的自动谓词推断:.filter不再需要手写守卫
原文档强调的一个重要演进是:TypeScript 5.5 起,编译器可以自动推断类型谓词(如x is T),尤其是在.filter这类函数中,编译器能够识别出undefined等值已被移除,从而给出更精确的结果类型、减少错误。
const nums = [1, null, 2].filter(x => x !== null);在 TypeScript 5.5 之前,上面这行代码的结果类型是(number | null)[]——尽管运行时null一定被过滤掉了,编译器却"看不到"这一点,开发者不得不手写(x): x is number => x !== null这类守卫(正如上文isValid那样)。而在 5.5 之后,编译器会为x => x !== null这种清晰明确的检查自动生成谓词,使nums的类型直接收窄为number[]。
什么情况下可以自动推断,什么情况下不能
原文档特别指出该特性存在边界:它适用于清晰明确的检查(例如x !== undefined、x !== null),但不适用于歧义检查,典型的反例是!!x:
// 可以自动推断:检查明确 const cleaned = items.filter(x => x !== undefined); // 结果类型正确移除 undefined // 不能自动推断:歧义检查 const truthy = items.filter(x => !!x); // !!x 只表达"真值性",编译器无法据此确定被保留值的具体类型原因在于:x !== undefined的语义与类型系统直接对应——它断言"值不是undefined",编译器能据此排除对应分支;而!!x是"真值"判断,它同时排除了0、''、NaN、null、undefined等多种值,编译器无法将这些值映射到某个确定的类型收窄结果,因此不会生成谓词。实际项目中,若确需"仅保留真值"且希望得到精确类型,仍应显式编写守卫函数或使用类型谓词标注。
与相关收窄机制的协同与对比
类型谓词并非孤立存在,它与 TypeScript 的其他收窄手段共同构成控制流分析(Control Flow Analysis)体系。控制流分析是 TypeScript 静态分析代码流向、按需推断变量类型的能力,其详细讲解见仓库中的 控制流分析章节。以下几类机制可作为理解谓词的参照:
typeof守卫:typeof x === 'number'直接收窄内置类型(string、number、boolean等),本质是语言内置的单行谓词;- 真值收窄:
if (name)可把string | null收窄为string,但无法表达!!x这类语义; - 相等性收窄:配合
===、!==和switch,把联合类型收窄到特定字面量分支; in运算符收窄:依据属性是否存在区分对象类型;instanceof收窄:依据构造函数区分类实例。
当这些内置手段无法表达"值满足某条自定义规则"时,类型谓词就是补全能力的那块拼图——你可以把任何运行时检查(正则匹配、结构校验、字段存在性等)封装成谓词,并让编译器信任其结果。
类型谓词 vs 断言函数(Assertion Functions)
仓库的 杂项章节 专门对比了类型谓词与断言函数的差异,两者极易混淆但定位不同:
// 类型守卫(Type Guard):返回布尔值,通过 true/false 告知结果 const isNumber = (value: unknown): value is number => typeof value === 'number'; // 断言函数(Assertion Function):不返回布尔值,条件不满足时直接抛错 function isNumber2(value: unknown): asserts value is number { if (typeof value !== 'number') { throw new Error('Not a number'); } }关键区别:
- 类型守卫:返回
boolean,谓词不成立时返回false,调用方自行决定分支走向; - 断言函数:返回类型标注为
asserts value is Type,不返回任何值;谓词不成立时抛出异常,只有通过断言之后,后续代码中的value才被收窄为Type。
两者的共同点是都通过运行时检查换取编译期的类型收窄;差异在于失败时的行为(返回false还是抛错)以及返回类型语法的不同。选择依据很简单:需要"分支处理"用类型守卫,需要"校验不通过即中断"用断言函数。
谓词与never的组合:穷尽性检查
类型谓词还可与never类型协作,实现联合类型的穷尽性检查(Exhaustiveness Checking)——确保switch或if覆盖了所有可能分支。仓库的 穷尽性检查章节 给出如下模式:
type Direction = 'up' | 'down'; const move = (direction: Direction) => { switch (direction) { case 'up': console.log('Moving up'); break; case 'down': console.log('Moving down'); break; default: const exhaustiveCheck: never = direction; // 若新增分支未处理,此处报错 console.log(exhaustiveCheck); } };其原理可理解为:收窄机制把default分支中的direction收窄为"剩余未处理成员";若所有成员都已被case覆盖,该类型就只剩never。此时若Direction新增一个'left'而未被处理,never类型赋值会立即产生编译错误。关于never的完整语义(表示永不发生的值,用于无限循环、必然抛错函数等),可参考 never 类型章节。这一模式常与用户自定义守卫配合,用于"解析函数收窄后仍确认所有情况已被处理"的场景。
使用建议与注意事项
- 优先使用内置收窄:
typeof、in、instanceof、相等性检查能解决大多数问题,不要为简单场景过度封装谓词; - 谓词必须名副其实:函数体必须真实执行与声明一致的检查,否则会破坏类型安全(谓词本身是"信任契约",TypeScript 不会验证函数体逻辑是否正确);
- 命名遵循
is/isValid惯例:如isString、isNumber、isDefined,便于阅读者一眼识别其语义; - 善用 5.5 自动推断:对
.filter(x => x !== null)这类明确检查,无需再手写守卫;遇到结果类型不符合预期时,再用自定义谓词兜底; - 注意歧义检查:
!!x、复杂的复合条件等场景不会触发自动推断,需要显式标注谓词; - 返回类型只对参数生效:谓词语法
param is Type中的param必须是函数参数名,不能指向外部变量。
小结
类型谓词是 TypeScript 类型系统中最实用的"自定义收窄"工具:它以value is Type返回类型把运行时布尔检查提升为编译期类型信息,让unknown、联合类型在条件分支中被精确收窄;TypeScript 5.5 又通过自动谓词推断,让.filter等常见场景免去手写守卫的负担。理解"明确检查可推断、歧义检查需显式声明"这条边界,再结合断言函数、never穷尽性检查等配套机制,你就能在 The Concise TypeScript Book 提供的知识体系之上,写出类型更精确、更少any断言、更少隐藏 bug 的 TypeScript 代码。相关收窄与类型系统的完整讲解,可继续阅读仓库内的 收窄章节、控制流分析章节 与 探索类型系统章节。
- 文档
- 教程
【免费下载链接】typescript-book
The Concise TypeScript Book: A Concise Guide to Effective Development in TypeScript. Free and Open Source.
相关推荐
The Concise TypeScript Book 精讲:类型谓词(Type Predicates)与 TypeScript 5.5 自动推断
The Concise TypeScript Book 精讲:类型谓词(Type Predicates)与 TypeScript 5.5 自动推断 导读 类型谓
文档教程The Concise TypeScript Book 深度解析:类型谓词(Type Predicates)——从手写类型守卫到 TypeScript 5.5 自动推断
The Concise TypeScript Book 深度解析:类型谓词(Type Predicates)——从手写类型守卫到 TypeScript 5.5
文档教程The Concise TypeScript Book:类型谓词(Type Predicates)实战指南——从自定义类型守卫到 TypeScript 5.5 的自动推断
The Concise TypeScript Book:类型谓词(Type Predicates)实战指南——从自定义类型守卫到 TypeScript 5.5
文档教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考