"as const" 在 TypeScript 中是什么意思,它的用例是什么?

What does the "as const" mean in TypeScript and what is its use case?

我对 as const 演员表感到困惑。看了几个文档和视频,没看懂。

我关心的是下面代码中的 as const 是什么意思,使用它有什么好处?

const args = [8, 5] as const;
const angle = Math.atan2(...args);
console.log(angle);

如果您要写 const args = [8, 5],那么没有什么能阻止您也写 args[0] = 23args.push(30) 或任何其他内容来修改该数组。你所做的只是告诉 TS/JS 名为 args 的变量指向那个特定的数组,所以你不能改变它引用的内容(例如你不能做 args = "something else")。您可以修改数组,但不能更改其变量指向的内容。

另一方面,现在将 as const 添加到声明中 确实 使其保持不变。整个东西是只读的,所以你根本不能修改数组。


澄清一下,正如评论中指出的那样:

"really makes it constant" could imply that there is some runtime effect when there is none. At runtime, args.push(30) will still modify the array. All as const does is make it so that the TypeScript compiler will complain if it sees you doing it. – jcalz

as const只影响编译器,它的只读作用有一个例外(见注释)。但总的来说,这仍然是 constas const 之间的主要使用差异。一个用于使引用不可变,另一个用于使引用的内容不可变。

这是一个 const 断言。 Here is a handy post on them, and here is the documentation.

When we construct new literal expressions with const assertions, we can signal to the language that

  • no literal types in that expression should be widened (e.g. no going from "hello" to string)
  • object literals get readonly properties
  • array literals become readonly tuples

对于 const args = [8, 5] as const;,第三个项目符号适用,tsc 将其理解为:

// Type: readonly [8, 5]
const args = [8, 5] as const;

// Ok
args[0];
args[1];

// Error: Tuple type 'readonly [8, 5]' of length '2' has no element at index '2'.
args[2];

没有断言:

// Type: number[]
const args = [8, 5];

// Ok
args[0];
args[1];

// Also Ok.
args[2];

这被称为 const assertionconst 断言告诉编译器推断出 narrowest*most specific 类型可以为一个表达式。如果您将其关闭,编译器将使用其默认类型推断行为,这可能会导致 widermore general 类型。

请注意,它被称为“断言”而不是“强制转换”。在 TypeScript 中通常要避免使用术语“cast”;当人们说“cast”时,他们通常暗示某种可以在运行时观察到的效果,但是 TypeScript 的类型系统,包括类型断言和 const 断言,完全 erased 来自发出的 JavaScript.因此,使用 as const 和不使用 在运行时 的程序绝对没有区别。


不过,在编译时,有一个明显的区别。让我们看看当您在上面的示例中省略 as const 时会发生什么:

const args = [8, 5];
// const args: number[]
const angle = Math.atan2(...args); // error! Expected 2 arguments, but got 0 or more.
console.log(angle);

编译器看到 const args = [8, 5]; 并推断出 number[] 的类型。这是零个或多个 number 类型元素的可变数组。编译器不知道 有多少 有哪些 元素。这样的推论通常是合理的;通常,数组内容是要以某种方式修改的。如果有人想写 args.push(17)args[0]++,他们会对 number[].

类型感到满意

不幸的是,下一行 Math.atan2(...args) 导致错误。 Math.atan2() 函数恰好需要两个数字参数。但是编译器只知道 args 是一个数字数组。它完全忘记了有两个元素,因此编译器抱怨说您在调用 Math.atan2() 时使用“0 个或多个”参数,而它恰好需要两个。


将其与带有 as const 的代码进行比较:

const args = [8, 5] as const;
// const args: readonly [8, 5]
const angle = Math.atan2(...args); // okay
console.log(angle);

现在编译器推断 args 是类型 readonly [8, 5]... 一个 readonly tuple 其值恰好是数字 85那个命令。具体来说,args.length 被编译器准确识别为 2

这足以让带有 Math.atan2() 的下一行工作。编译器知道 Math.atan2(...args)Math.atan2(8, 5) 相同,这是一个有效的调用。


再说一次:在运行时,没有任何区别。两个版本都将 1.0121970114513341 记录到控制台。但是 const 断言,就像静态类型系统的其余部分一样,并不意味着在运行时产生影响。相反,它们让编译器更多地了解代码的意图,并且可以更准确地分辨出正确代码和错误之间的区别。

Playground link to code


* 这对于数组和元组类型来说并不严格; readonly 数组或元组在技术上比可变版本 更宽 。可变数组被认为是 readonly 数组的子类型;不知道前者有像 push() 这样的变异方法,而后者有。

简而言之,它可以让你创建完全只读的对象,这被称为const assertion,在你的代码中as const意味着数组位置值为 readonly,以下是其工作原理的示例:

const args = [8, 5] as const;
args[0] = 3;  // throws "Cannot assign to '0' because it is a read-only     
args.push(3); // throws "Property 'push' does not exist on type 'readonly [8, 5]'"

你可以在最后一个抛出的错误中看到,args = [8, 5] as const 被解释为 args: readonly [8, 5],那是因为 the first declaration is equivalent to a readonly tuple.

断言有一些例外 'fully readonly',您可以查看它们 here。但是,一般的好处是 readonly 行为被添加到 它的所有对象属性

const args = [8, 5];

// Without `as const` assert; `args` stills a constant, but you can modify its attributes
args[0] = 3;  // -- WORKS
args.push(3); // -- WORKS

// You are only prevented from assigning values directly to your variable
args = 7;     // -- THROWS ERROR

有关更多详细信息,以下是帮助我理解 const 断言的其他相关 question/answers 的列表:

  1. How do I declare a read-only array tuple in TypeScript?
  2. Typescript readonly tuples