- 文档
- 教程
【免费下载链接】typescript-book
The Concise TypeScript Book: A Concise Guide to Effective Development in TypeScript. Free and Open Source.
unknown是 TypeScript 中专门表示"类型未知"值的类型,它允许你安全地接收来自外部 API、用户输入或第三方库的任意数据,同时强制你在真正使用这些值之前完成类型检查(type check)或类型断言(type assertion)。读完本文,你将掌握unknown与any的本质区别、它的赋值(assignability)规则、配合类型收窄(narrowing)的正确使用方式,以及在真实项目中用unknown加固代码安全性的完整实战方案。本文内容以 The Concise TypeScript Book 开源仓库的 "Unknown type" 章节(及其意大利语翻译版 unknown-type.md)为骨架展开。
什么是unknown类型
在 TypeScript 中,unknown类型表示一个类型未知的值(a value that is of an unknown type)。它最核心的设计意图是:你可以把一个不确定来源的值声明为unknown,但在对它执行任何具体操作之前,编译器会强制你先完成类型检查或类型收窄,将其限定到一个更具体的类型上。
与any类型不同——any允许对值做任意操作而不会报错——unknown不允许在没有先断言(assert)或收窄(narrow)到更具体类型的情况下进行任何操作。这正是它被称为 "type-safe alternative toany"(any的类型安全替代品)的根本原因。
从仓库结构看,该章节位于本书 "Primitive Types / Built-in Types" 体系中的第 46 节(sidebar order: 46),紧随第 45 节 Tipo Any(英文版 any-type.md)之后,二者在目录设计上就构成了"宽松任意类型 vs 安全未知类型"的对照关系。
unknown与any的核心区别
对比两个类型的章节定义可以清晰看到设计定位的差异:
| 维度 | any(any-type.md) | unknown(unknown-type.md) |
|---|---|---|
| 含义 | 通用超类型(universal supertype),可表示任何类型的值(基本类型、对象、数组、函数、错误、symbol) | 表示类型未知的值 |
| 操作限制 | 无任何限制,编译器放行所有操作 | 使用前必须先做类型检查、断言或收窄 |
| 类型安全 | 会削弱类型安全,容易让错误"静默"扩散 | 是any的类型安全替代品 |
| 赋值能力 | 可赋给任意类型 | 只能赋给any和unknown本身 |
| 适用场景 | 编译期类型未知、外部无类型定义的 API/库(需谨慎限量使用) | 需要接收未知数据、同时保留类型安全检查的场景 |
原文档还特别给出了使用any的纪律建议(见于 any-type.md),这些建议反向印证了unknown的设计动机:
- 将
any的使用限制在"类型确实未知"的特定场景; - 不要让函数返回
any,否则会削弱调用方代码的类型安全; - 如果只是想屏蔽编译器报错,优先用
@ts-ignore而不是any。
当你在编写代码时发现"不得不引入any",第一反应应该换成unknown+ 类型收窄,这正是本书把unknown章节紧排在any之后的教学意图。
unknown的赋值规则(assignability)
原文档明确给出了unknown类型唯一的核心规则:unknown只能赋值给any和unknown自身,不能赋值给任何其他具体类型。
原文档中的完整示例(含中文注释):
let value: unknown; let value1: unknown = value; // Valido —— 合法:unknown 可赋给 unknown let value2: any = value; // Valido —— 合法:unknown 可赋给 any let value3: boolean = value; // Non valido —— 非法:unknown 不能直接赋给 boolean let value4: number = value; // Non valido —— 非法:unknown 不能直接赋给 number这一规则的含义非常实际:从unknown到具体类型的"下行赋值"被严格禁止。当你把某个值声明为unknown后,必须先通过类型收窄(narrowing)或类型断言将其"洗白"成具体类型,才能赋给boolean、number、string等目标类型。反过来,任意类型的值都可以赋给unknown(上行方向不受限),因为"任意值在编译期看来都可能是未知类型"是安全的方向。
相关知识点:赋值收窄(Assignments narrowing)
虽然unknown本身不能直接赋给具体类型,但一旦你在代码中把某个联合类型的变量赋上具体值,TypeScript 会基于赋值语句自动收窄其类型(见本书第 22 节 assignments.md):
let value: string | number; value = 'hello'; if (typeof value === 'string') { console.log(value.toUpperCase()); // 此处 value 被收窄为 string } value = 42; if (typeof value === 'number') { console.log(value.toFixed(2)); // 此处 value 被收窄为 number }这套"赋值 → 收窄 → 安全使用"的机制,与unknown的"检查 → 收窄 → 安全使用"在使用心智上是完全一致的:TypeScript 只允许你在类型信息被确认之后执行对应操作。
对unknown使用类型守卫(typeof type guard)
既然unknown不允许直接操作,那如何"安全地使用"它?原文档给出了最典型的实战模式——用typeof类型守卫收窄后再运算:
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)); // non definito(undefined)这段代码展示了unknown的完整安全使用闭环:
- 函数的两个入参被声明为
unknown,因此无论调用方传入数字、字符串还是其他任意值,编译期都不报错; - 在运算前用
typeof a === 'number' && typeof b === 'number'做双重检查; - 只有两个参数都被确认为
number时,才会执行a + b算术运算并返回number; - 任何一方不是数字时,走
undefined分支,返回值类型声明为number | undefined。
注意:没有typeof检查时直接写a + b是编译错误,因为unknown上不允许任何操作——这正是unknown相对于any的保护价值所在。typeof检查在运行时也会真实执行,所以这段代码在 JavaScript 层面同样安全,不会出现字符串与数字相加这类隐式转换的脏数据。
更多收窄手段(源于本书第 21 节 narrowing.md)
unknown收窄并不局限于typeof,本书 narrowing.md 列出了 TypeScript 官方支持的全部收窄手段,均可配合unknown使用:
- typeof 类型守卫:基于 JavaScript 内置类型(
number、string、boolean、object、function、undefined、symbol、bigint)检查来收窄; - 真值性收窄(truthiness narrowing):通过判断变量是否为 truthy/falsy 来排除
null、undefined、0、''等分支; - 相等性收窄(equality narrowing):结合
switch与===、!==、==、!=运算符,按具体值收窄; in运算符收窄:依据对象是否拥有某属性(如'breed' in pet)来区分对象类型;instanceof收窄:依据构造函数实例关系收窄到具体类。
例如,用instanceof处理类实例型的unknown:
class Square { constructor(public width: number) {} } class Rectangle { constructor(public width: number, public height: number) {} } function area(shape: unknown) { if (shape instanceof Square) { return shape.width * shape.width; } else if (shape instanceof Rectangle) { return shape.width * shape.height; } return undefined; }控制流分析对收窄的影响(源于本书第 23 节 control-flow-analysis.md)
unknown的收窄依赖 TypeScript 的静态控制流分析(Control Flow Analysis)。本书 control-flow-analysis.md 指出:自 TypeScript 4.4 起,控制流分析不仅作用于if语句块内,还能作用于条件表达式以及通过const变量间接引用的判别属性:
const f1 = (x: unknown) => { const isString = typeof x === 'string'; if (isString) { x.length; // OK:通过 const 变量 isString 间接收窄了 x } };同时文档也明确列出了不会发生收窄的反例,值得在实战中警惕:
const f1 = (x: unknown) => { let isString = typeof x === 'string'; // 用 let 声明,不是 const if (isString) { x.length; // Error:isString 不是 const,无法间接收窄 x } }; const f6 = (obj: { kind: 'foo'; foo: string } | { kind: 'bar'; bar: number }) => { const isFoo = obj.kind === 'foo'; obj = obj; // 函数体内对 obj 发生了重新赋值 if (isFoo) { obj.foo; // Error:obj 在函数体内被赋值,收窄信息失效 } };此外,条件表达式中的间接分析最多支持五层间接引用(Up to five levels of indirection)。这意味着当你用unknown接收数据时,请尽量把类型检查结果保存在const变量中、避免在检查后重新赋值原变量,否则收窄会静默失效,操作会重新报错。
类型谓词(Type Predicates)自定义收窄函数(源于本书第 24 节)
当typeof、instanceof等内置收窄手段不足以描述你的业务判断时,可以用类型谓词函数自定义收窄逻辑,它特别适合处理unknown入参(见 type-predicates.md):
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'); } };同一章节还提到 TypeScript 5.5 起会自动推断类型谓词(如x is T),因此.filter(x => x !== null)这类清晰检查可以自动移除null,得到更精确的类型;但!!x这类模糊检查不会触发推断。
实战:用unknown安全处理外部数据
把上述知识组合起来,最常见的落地场景是解析外部不可信数据(API 响应、JSON 解析、表单输入)。以经典的JSON.parse为例:
// 推荐:先以 unknown 接收,再逐步收窄 function parseJson(input: string): unknown { return JSON.parse(input); } const data: unknown = parseJson('{"name":"TypeScript","version":5.5}'); if ( typeof data === 'object' && data !== null && 'name' in data && typeof (data as { name: unknown }).name === 'string' ) { const name: string = (data as { name: string }).name; // 安全提取 console.log(name.toUpperCase()); }这里遵循了unknown的完整纪律:先确认是对象、排除null(因为typeof null === 'object')、再用in运算符确认属性存在、最后用typeof确认属性值的具体类型,每一步都在编译器监督下进行。你也可以把这段判断抽取成类型谓词函数(参考上面的isString),让收窄逻辑可复用。
结合原文档的add示例,一个完整的防御式工具函数可以这样写:
const toNumber = (value: unknown): number | undefined => typeof value === 'number' ? value : undefined; const addSafe = (a: unknown, b: unknown): number | undefined => { const na = toNumber(a); const nb = toNumber(b); return na !== undefined && nb !== undefined ? na + nb : undefined; };最佳实践小结
综合原文档与仓库中 any-type.md、narrowing.md、type-predicates.md、control-flow-analysis.md 各章节,使用unknown时可以遵循以下准则:
- 优先用
unknown替代any:需要表达"类型未知"时,unknown是安全默认项,any只保留给确有必要的少量场景; - 使用时必须收窄:
typeof、instanceof、in、真值性、相等性收窄,以及自定义类型谓词,都是unknown的合法"解锁"方式; - 把收窄结果保存在
const中:避免let与函数体内重新赋值,防止控制流分析失效; - 不要让
unknown泄漏回any或具体类型:未经检查就断言(如value as string)会绕过编译期保护,应仅在收窄逻辑完备时使用; - 函数边界处隔离:在模块/函数边界用
unknown接收外部数据,在内部完成校验后输出强类型结果(类似parseJson → 校验 → 强类型 DTO的模式),让"不安全"范围最小化。
延伸阅读
- 与
unknown对照的类型:Tipo Any / any-type.md - 类型收窄全集:narrowing.md
- 控制流分析与收窄限制:control-flow-analysis.md
- 自定义收窄与 TS 5.5 谓词推断:type-predicates.md
- 赋值触发的收窄:assignments.md
- 从不发生的类型
never(常与unknown搭配做穷尽检查):never-type.md
- 文档
- 教程
【免费下载链接】typescript-book
The Concise TypeScript Book: A Concise Guide to Effective Development in TypeScript. Free and Open Source.
相关推荐
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 Concise TypeScri
文档教程TypeScript 的 any 类型全解:通用超类型本质、noImplicitAny 防线与 unknown 替代方案(The Concise TypeScript Book 实战指南)
TypeScript 的 any 类型全解:通用超类型本质、noImplicitAny 防线与 unknown 替代方案(The Concise TypeScr
文档教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考