TypeScript 中的“as const”是啥意思,它的用例是啥?

Posted

技术标签:

【中文标题】TypeScript 中的“as const”是啥意思,它的用例是啥?【英文标题】:What does the "as const" mean in TypeScript and what is its use case?TypeScript 中的“as const”是什么意思,它的用例是什么? 【发布时间】:2021-07-03 16:34:14 【问题描述】:

我对@9​​87654324@ 演员感到困惑。我查看了一些文档和视频,但没有完全理解。

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

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

【问题讨论】:

他们是const蚂蚁。声明后无法更改。就像1 在数学中永远是one (1) 一样,数字是常数,因为它们不能改变。 见:dev.to/adamcoster/the-typescript-as-const-trick-2f4o What's the difference between TypeScript const assertions and declarations?的可能重复 你读过the documentation吗? @jcalz 我认为我们可以保持开放状态,让这个问题成为“什么是 const”的规范。你的回答真的很棒??? 【参考方案1】:

这称为const assertion。 const 断言告诉编译器推断它可以为表达式推断出 最窄*最具体 类型。如果您不使用它,编译器将使用其默认的类型推断行为,这可能会导致 更宽更通用 类型。

请注意,它被称为“断言”而不是“强制转换”。在 TypeScript 中通常要避免使用“cast”一词;当人们说“cast”时,他们通常暗示某种可以在运行时观察到的效果,但是 TypeScript 的类型系统,包括类型断言和const 断言,完全是来自发出的 javascript 的erased。因此,使用as const 的程序和不使用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 断言。 ? 我现在对下面的例子感到困惑。我可以知道为什么该示例不起作用吗?让 b = (60 * 60 * 1000) 作为常量;实际上,文档中提到 const 断言只能立即应用于简单的文字表达式。但是,括号的内部看起来没问题。我错过了什么?谢谢。 @CihatŞaman - “'const' 断言只能应用于对枚举成员或字符串、数字、布尔值、数组或对象文字的引用。”。 60 * 60 * 1000 不是文字,它是计算出来的,有关此问题的更多详细信息,请参阅 the PR 介绍它们。 adding math 上存在文字类型的未解决问题【参考方案2】:

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

当我们用 const 断言构造新的文字表达式时,我们可以向语言发出信号

不应扩大该表达式中的文字类型(例如,不要从“hello”变为字符串) 对象字面量获得只读属性 数组字面量变成只读元组

对于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 断言也可以访问“长度”属性,比如args['length'] @The.Wolfgang.Grimmer 因为技术上元组类型仍然是数组。 我明白了,似乎length 是数组对象中的唯一属性。感谢您的快速回复。【参考方案3】:

简而言之,它可以让您创建完全只读的对象,这被称为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 等价于readonly tuple。

断言“完全只读”有一些例外,您可以检查它们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 断言的其他相关问题/答案的列表:

    How do I declare a read-only array tuple in TypeScript? Any difference between type assertions and the newer as operator in TypeScript? Typescript readonly tuples

【讨论】:

【参考方案4】:

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

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


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

“真的让它保持不变”可能意味着当没有运行时效果时会有一些运行时效果。在运行时,args.push(30) 仍然会修改数组。正如 const 所做的那样,如果 TypeScript 编译器看到你这样做,它就会抱怨。 – jcalz

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

【讨论】:

如果知道我做错了什么会很感激吗? 不能代表投反对票的人。也许他们认为您的结尾语句模棱两可(“现在将 as const 添加到声明中确实使它成为常量”)? as const 并非所有声明都是完全只读的:请参阅 const assertions caveats。 也不知道反对票,但“真的让它保持不变”可能意味着当没有投票时会有一些运行时影响。在运行时,args.push(30) 仍然会修改数组。 as const 所做的只是让 TypeScript 编译器在看到你这样做时会抱怨。

以上是关于TypeScript 中的“as const”是啥意思,它的用例是啥?的主要内容,如果未能解决你的问题,请参考以下文章

TypeScript const 和 const 与 as const

[TypeScript] as const, force immutability for Object type

TypeScript 中的“类型”保留字是啥?

TypeScript 中的“keyof typeof”是啥意思?

Flow 中星号 (*) 类型的用途是啥,TypeScript 中的等价物是啥?

Typescript 中的“声明全局”是啥?