The Concise TypeScript Book 系列:深入解析 unknown 类型——类型安全的 any 替代方案
文档教程【免费下载链接】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点击查看免费下载在 TypeScript 中unknown类型表示一个类型未知的值。与允许任何类型值、完全绕过类型检查的any不同unknown要求在使用前先进行类型检查type check或类型断言assertion / narrowing到更具体的类型否则编译器禁止任何操作。因此unknown是唯一同时满足可接收任意值与可静态约束后续操作的类型安全替代方案在解析外部 API 数据、编写通用工具函数、处理catch子句错误对象等场景中是替代any的首选。读完本文你将掌握unknown的赋值规则、安全收窄方法并能写出既灵活又不牺牲类型安全的代码。unknown 与 any 的本质区别any被称作通用超类型universal supertype可以表示任意类型的值原始类型、对象、数组、函数、错误、symbol 等常用于编译期无法确定类型、或对接没有 TypeScript 类型定义的外部 API 与库的场景。代价是声明为any的值不受任何限制编译器不会阻止对它的任何操作类型检查形同虚设见 Any type。unknown则完全不同它同样是所有值的容器可以接收任何类型的值但禁止在其上执行任何操作——除非先通过断言或收窄把它变成更具体的类型。let value: unknown; let value1: unknown value; // Valid let value2: any value; // Valid let value3: boolean value; // Invalid let value4: number value; // Invalid从这个例子可以总结出unknown的核心赋值规则unknown只能赋值给any和unknown自身不能直接赋值给任何其他具体类型。试图把unknown赋给boolean或number都会触发编译错误。从类型即集合的视角理解 unknownExploring the Type System 用集合论解释了 TypeScript 的类型系统每个类型都是一组可能值的集合。any与unknown都被视为全集universal set——每个值都是any的元素每个集合都是any的子集而unknown是any的类型安全对应物。也就是说从可容纳性上看所有类型都可赋值给unknown正如所有值都属于全集但从可用性上看unknown自身却只能赋值给any或unknown必须经过收窄才能落地为具体类型。这个不对称性正是unknown安全性的来源。为什么 unknown 比 any 更安全编译期禁止随意操作声明为any的值可以直接参与运算、调用方法、访问属性编译器不做任何阻拦而声明为unknown的值在收窄之前任何访问都会产生编译错误。例如let anything: any; anything.foo.bar(); // 编译器不报错运行期可能崩溃 let value: unknown; value.foo; // Error: Object is of type unknown value(); // Error: Object is of type unknown这种先收窄、后使用的强制约束避免了大量本可在编译期拦截的运行时错误。因此社区与本书的建议是能用unknown就尽量不用any详见 Any type 中限制any的使用、不要从函数返回any的实践建议。安全使用 unknown 的三大收窄手段收窄narrowing是把宽泛类型细化为更具体类型的过程。TypeScript 通过多种方式对unknown进行收窄最常用的是typeof类型守卫、instanceof、in运算符、真值/相等性检查以及用户自定义类型守卫详见 Narrowing 与 Type Predicates。手段一typeof 类型守卫typeof类型守卫依据 JavaScript 内建类型检查来缩小变量类型。下面是与原文档一致的可直接运行的示例一个接受两个unknown参数、只有双方都是number时才执行加法的安全函数。const add (a: unknown, b: unknown): number | undefined typeof a number typeof b number ? a b : undefined; console.log(add(1, 2)); // 3 console.log(add(x, 2)); // undefined这里返回类型被声明为number | undefined既保证了类型安全两个参数必须是数字才会相加又避免了传入非数字时抛出运行时异常。注意typeof守卫只能覆盖原始类型string、number、boolean、symbol、bigint、undefined、function、object无法区分数组、对象或具体的类实例。手段二instanceof / in 运算符对于对象与类实例可以使用instanceof基于构造函数收窄或用in运算符基于属性存在性收窄class Square { constructor(public width: number) {} } function area(shape: unknown): number | undefined { if (shape instanceof Square) { return shape.width * shape.width; // shape 已收窄为 Square } return undefined; } type Dog { name: string; breed: string }; type Cat { name: string; likesCream: boolean }; const describe (pet: unknown): string | undefined { if (pet typeof pet object breed in pet) { return dog; } if (pet typeof pet object likesCream in pet) { return cat; } return undefined; };手段三用户自定义类型守卫Type Predicates当内置收窄手段无法覆盖业务判断时可以编写返回类型谓词value is T的辅助函数把unknown收窄到任意自定义类型const isString (value: unknown): value is string typeof value string; const foo (bar: unknown) { if (isString(bar)) { console.log(bar.toUpperCase()); // bar 收窄为 string } else { console.log(not a string); } };该模式同样适用于Array.isArray、Number.isFinite等内建判断函数可参考 Type Predicates 中的.filter场景TypeScript 5.5 起会自动推断类型谓词如x ! undefined这类明确检查让过滤后的数组类型更精确。补充控制流分析与 const 别名收窄TypeScript 的控制流分析Control Flow Analysis使unknown的收窄能够穿透临时变量只要用const保存收窄判断结果后续分支依然生效TypeScript 4.4 起支持条件表达式与经const间接引用的判别属性详见 Control Flow Analysisconst f1 (x: unknown) { const isString typeof x string; if (isString) { x.length; // Validx 已收窄为 string } }; const f2 (x: unknown) { let isString typeof x string; if (isString) { x.length; // ErrorisString 不是 const收窄不生效 } };注意若判断结果保存在let变量中、或函数体内对原变量重新赋值收窄分析将失效。实用场景一解析外部数据与 API 返回值unknown最常见的用途是标注来源不可信的数据。例如JSON.parse的返回值、第三方 API 的响应体在编译期我们无法确知其真实结构// 接收外部数据先声明为 unknown const parseJson (text: string): unknown { return JSON.parse(text); }; const data: unknown parseJson({name:typescript,version:5.5}); // 使用前必须逐层收窄 if ( data typeof data object name in data typeof (data as Recordstring, unknown).name string ) { console.log((data as { name: string }).name.toUpperCase()); }把解析结果先声明为unknown再通过逐层守卫收窄可确保访问name属性前其类型已被验证为string避免data.name在运行时直接抛错。实用场景二安全解析 JSON 的健壮函数结合上面的原则可以编写一个通用的、类型安全的 JSON 解析函数——只有结构完全匹配时才返回数据否则返回undefinedconst safeParse (text: string): unknown { try { return JSON.parse(text); } catch { return undefined; // JSON.parse 失败时安全降级 } }; const isPerson (value: unknown): value is { name: string; age: number } { if (!value || typeof value ! object) return false; const v value as Recordstring, unknown; return typeof v.name string typeof v.age number; }; const person: unknown safeParse({name:Ada,age:36}); if (isPerson(person)) { // person 此时收窄为 { name: string; age: number } console.log(${person.name}, ${person.age} years old); } else { console.log(data does not match the expected shape); }这里的关键是类型守卫函数isPerson返回value is {...}谓词将逐字段校验封装成可复用的安全通道模式详见 Type Predicates。实用场景三catch 子句中的错误对象TypeScript 的catch子句变量默认是unknown开启useUnknownInCatchVariables它是strict的一部分。因此对错误对象的处理必须显式收窄try { throw new TypeError(boom); } catch (error) { // error 的类型是 unknown必须先收窄再访问 message if (error instanceof TypeError) { console.log(TypeError:, error.message); } else if (error instanceof Error) { console.log(Error:, error.message); } else if (typeof error string) { console.log(String error:, error); } else { console.log(Unknown error:, error); } }这种写法与 Others 中使用标准 JavaScript 错误处理机制 instanceof区分错误类型 自定义 Error 子类的最佳实践完全一致且unknown强制你处理所有可能分支。unknown 在类型系统中的更多配合用法unknown的价值还体现在与类型系统其他特性的协同上弱类型Weak Types索引签名当类型只包含全可选属性时可向其索引签名追加unknown以兼容任意传入对象见 Exploring the Type Systemtype Options { [prop: string]: unknown; // 允许任意键 a?: string; b?: string; }; const fn (options: Options) undefined; fn({ c: c }); // Validunknown与字面量/联合类型结合作为安全的兜底分支例如string | number | unknown本质上收敛为unknown可用来表达已知类型之外的任何值。配合never与void理解赋值规则Exploring the Type System 总结了完整的赋值矩阵所有类型都可赋值给unknownunknown只能赋值给自身与anynever不能接收任何值void除any外不与其他类型互通。配合 strictNullChecks开启 strictNullChecks 后null与undefined只能显式赋给声明为null | undefined联合类型的变量。当从可能为空的接口数据中收窄时先判断value ! null value ! undefined再收窄到具体类型与unknown的逐层校验相辅相成。与 any、void、never 的横向对比类型可接收的赋值自身可被赋值给允许的操作典型用途any任意类型任意类型任意操作不检查无类型定义的外部库、渐进迁移unknown任意类型仅any、unknown收窄/断言后允许外部数据、catch错误、通用入参void除any外几乎不可仅any无不返回值的函数never无空集合任意类型无永不返回的函数、穷尽性检查关于any的使用建议详见 Any type仅在类型确实未知的少数场景使用any不要从函数返回any需要静默编译器时优先使用ts-ignore。而unknown正是为这些场景提供的安全答案。小结unknown是 TypeScript 内置原语中兼具容纳任意值与强制类型检查的独特类型本书目录中它与any、void、never一起构成特殊类型章节见 Table of Contents 与 Built-in Type Primitives。核心要点unknown可以接收任何类型的值但只能赋值给any和unknown自身使用前必须通过typeof、instanceof、in、真值/相等性检查或自定义类型谓词进行收窄优先用unknown替代any处理外部 API 数据、JSON.parse结果和catch错误对象把类型错误拦截在编译期。赞分享文档教程【免费下载链接】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点击查看免费下载相关推荐The Concise TypeScript Book深入理解 unknown 类型——any 的类型安全替代方案The Concise TypeScript Book深入理解 unknown 类型——any 的类型安全替代方案 《The Concise TypeScri文档教程The Concise TypeScript Book 深度解析unknown 类型 —— 类型安全的 any 替代方案The Concise TypeScript Book 深度解析 unknown 类型 —— 类型安全的 any 替代方案 unknown 是 TypeScr文档教程The Concise TypeScript Book 精读unknown 类型——any 的安全替代与类型收窄实战The Concise TypeScript Book 精读unknown 类型——any 的安全替代与类型收窄实战 本篇技术指南以开源项目 The Conc文档教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考