Menu

TypeScript as:类型断言与类型转换详解

as 关键字让 TypeScript 把一个值当作另一种类型来对待。它不是类型转换:运行时既不转换也不检查任何东西。本文讲解 as 和尖括号语法、编译器允许哪些断言、经由 unknown 的双重断言,以及什么时候类型守卫是更好的选择。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

value as Type 是类型断言:它告诉 TypeScript 把 value 当作 Type 来对待。人们常把它叫作类型转换,但它只是给编译器的一条指令。它会从 JavaScript 输出中擦除,运行时既不转换任何东西,也不检查任何东西。

这是最典型的用法:你对某个值的了解比编译器多(这里是一段 JSON 的结构),于是把它说出来。如果你说错了,没有任何东西会提醒你。下面几节说明这意味着什么,以及什么时候运行时检查是更好的选择。

as 与尖括号语法

同一个断言有两种写法:

const someValue: unknown = "hello";
const a = someValue as string;   // as syntax
const b = <string>someValue;     // angle-bracket syntax, same meaning

尖括号写法不能用在 .tsx 文件中,因为 <string> 会被当作 JSX 标签。处处都用 as,就永远不会遇到这个问题。断言的优先级较低,所以要在后面继续写表达式时,请用括号包起来:(value as string).length。

断言不会转换值

这一点才是真正导致 bug 的地方。断言改变的是编译器对值的认知,而不是值本身:

编译器认为 asserted 是 number,所以 asserted + 1 作为算术运算通过了类型检查。但在运行时它仍然是字符串 "42",JavaScript 做的是字符串拼接。要改变一个值的类型,就要转换它:Number(x)、String(x)、Boolean(x)、BigInt(x)、new Date(x)。字符串转数字 页面对比了这些转换函数。

你想要写法运行时效果
告诉编译器一个你已知的类型x as T无
把字符串变成数字Number(x)、parseInt(x, 10)转换
把任意值变成字符串String(x)、`${x}`转换
先检查类型类型守卫、typeof、instanceof检查

编译器允许哪些断言

as 并非毫无限制。当一个类型可以赋值给另一个类型时,TypeScript 才允许 x as T:放宽("a" as string、dog as Animal)和收窄(animal as Dog、unknown as User)都可以。如果两个类型完全没有重叠,它就会拒绝:

编译器报告 index.ts(3,11): error TS2352: Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. 这条消息本身就点出了绕过的办法:input as unknown as number。这个双重断言能通过编译,但在运行时和上面的例子错得一模一样。当你觉得需要它的时候,正确的修复通常是做一次转换(Number(input))或者换一个类型。

对于对象,重叠规则很宽松。一个对象字面量只要有部分属性就会被接受,as 就是这样悄悄放过不完整的对象的:

类型注解(const draft: User = { name: "Ada" })或 satisfies User 都会报告缺少 email(TS2741)。只有当你确实打算稍后补全对象时,才在对象字面量上使用 as;更好的做法是直接构造完整的对象。

as const 不一样

as const 看起来像断言,作用却和放宽相反:它让字面量的类型尽可能窄。字符串保持为字面量类型,数组变成只读元组,对象属性变成 readonly。

它是安全的,因为它精确地描述了这个字面量,而不是声称编译器看不到的东西。(isSize 中的 sizes as readonly string[] 是一个放宽断言,同样安全:它让 includes 能接受任意字符串。)更多内容见 字面量类型。

什么时候类型守卫更合适

as 是一种声明,类型守卫是一种检查。在数据从代码外部进入的边界处(JSON、fetch、localStorage、用户输入、消息),这种声明可能是假的,而断言会把边界处一个清晰的错误,变成别处一个令人困惑的错误。

几种看起来相似的工具,大致这样区分:

工具编译期检查运行时检查适用场景
类型注解 const x: T = ...是,完整检查否值由你自己构造
satisfies T是,完整检查,并保留推断类型否对象字面量、配置
as T只检查“类型是否有重叠”否你比编译器知道得更多
x!只去掉 null / undefined否你确定值已经设置
类型守卫 x is T守卫的函数体就是普通代码是来自外部的数据

as 仍有两种合理用法:收窄编译器无法追踪的东西(两行之前刚设置的 Map 条目、来自无类型库的值),以及在测试代码中构造不完整的测试数据。让这些断言保持简短,并且紧挨着你能确认声明成立的地方。

常见问题

TypeScript 中 as 有什么作用?

value as Type 是类型断言:它告诉编译器从这里开始把 value 当作 Type。它会从编译后的 JavaScript 中移除,所以既不做转换,也不做运行时检查。如果断言是错的,程序会在之后使用这个错误类型的地方出问题。

在 TypeScript 中如何进行类型转换?

TypeScript 没有运行时类型转换。当你比编译器知道得更多时,用 as(或旧写法 <Type>value)改变静态类型。要真正转换一个值,就调用函数:Number("42")、String(42)、Boolean(x)、new Date(text)。

TypeScript 中的“as unknown as”是什么意思?

这是双重断言。当两个类型完全没有重叠时,TypeScript 会拒绝 x as T(错误 TS2352),而先经过 unknown 就能绕过这个检查,因为任何类型都可以断言为 unknown,unknown 也可以断言为任何类型。它会彻底关闭对这个值的类型检查,所以只在测试代码,或者已经用其他方式确认过类型的代码中使用。

TypeScript 中 as 和尖括号有什么区别?

含义上没有区别:<string>value 和 value as string 是同一个断言。尖括号写法不能用在 .tsx 文件中,因为它和 JSX 冲突,所以大家都用 as。

as 和 satisfies 有什么区别?

as 会覆盖推断出的类型,几乎不做检查(允许缺少属性)。satisfies 会对照类型检查这个值,报告缺少或多余的属性,同时保留精确的推断类型。对象字面量优先用 satisfies,只有在你确实比编译器知道得更多时才用 as。

Coddy 编程语言插图

和 Coddy 一起学编程

开始