首页 TypeScript TypeScript const 断言

TypeScript const 断言

0 1.7K

原文地址:https://medium.com/@islizeqiang/why-are-const-assertions-a-gem-in-typescript-e1d353f5d8ce

本文将介绍 TypeScript const断言。使用const断言可以减少很多笨重的类型声明。

首先,我们要来看一下,什么是const断言。

我们从 TypeScript 的官方文档中摘出来:

TypeScript 3.4 introduces a new construct for literal values called const assertions. Its syntax is a type assertion with const in place of the type name (e.g. 123 as const). 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

接下来,我们会一条一条解释。

no literal types in that expression should be widened

表达式中的字面量类型不会被放宽

首先,我们要知道,什么是字面量类型的放宽:

let method = 'GET';

我们使用了let关键字声明变量。

此时,method类型推断为string。这意味着,所有 string 类型的值都可以赋值给method

let method = 'GET';

method = 'POST';

这可能不是我们想要的,毕竟,method只能是GET。于是,我们把method换用const声明:

const method = 'GET';

现在,method类型是"GET"。这意味着,method的类型被收窄到了"Get"。此时,你不能给method赋别的什么值。这与 JavaScript 的规则是一致的:使用let声明的变量意味着以后可能会被改变,使用const声明的变量则不允许改变其值。

所谓字面量类型就是能够精确描述一个值的类型,例如一个指定的字符串(例如上面的"GET"),一个指定的数字、boolean值或者一个具体的枚举值。

const断言帮助我们达到这一目的,生成不可扩展的字面量类型。这意味着这种类型不能被放宽到更一般的类型:

现在,method并没有被扩展到string类型。当然,这只是一个简单的示例,其效果与const声明相同。如果你真的是要声明一个常量,应该使用const声明,而不是const断言。

那么,const断言可以用来干什么?

比如,有这么一个函数声明:

const request = (url: string, method: 'GET' | 'POST') => fetch(url, { method });

同时,我们有一个映射表,来保存method常量:

为什么会报错?因为 TypeScript 会把METHOD_MAP的类型推断为:

所以,为什么METHOD_MAP.GET的类型被推断为string,而不是"GET"?事实上,我们的确使用了const去声明METHOD_MAP。我们看另外一个例子:

// { name: string; age: number; }
const person = {
  name: '1',
  age: 2,
};
// OK
person.age = 3;

这个例子显示出,TypeScript 并没有针对类型做进一步的推断,从而因此保证了类型的灵活性。试想,如果在这个例子中,person类型被推断为{ name: '1', age: 2 },那么,我们就不得不为每一种person创建一个类型了。

在这个时候,我们就可以使用const断言声明METHOD_MAP的类型:

Object literal gets read-only property

对象字面量获得只读属性

给每一个属性添加const断言的确太麻烦了,我们可以给整个对象添加const断言。

这种语法同时添加了readonly修饰符。

另外,我们还可以使用尖括号语法:

const METHOD_MAP = <const>{
  GET: 'GET',
  POST: 'POST',
};
// Cannot assign to 'GET' because it is a read-only property
METHOD_MAP.GET = "GET"

给整个对象添加尖括号语法和对单个属性添加是有区别的:

array literals become readonly tuples

数组字面量变成readonly元组

这很容易理解:

现在,我们简单介绍过 TypeScript 的const断言。

总的来说,const断言允许 TypeScript 推断表达式中更具体的类型。在使用时,我们还需要注意一点,表达式语句。

例如,const断言只能放在简单字面量表达式后面:

// 错误!const 断言只能被应用到字符串、数字、布尔、数组或对象字面量
let a = (Math.random() < 0.5 ? 0 : 1) as const;
let b = (60 * 60 * 1000) as const;
// 成功!
let c = Math.random() < 0.5 ? (0 as const) : (1 as const);
let d = 3_600_000 as const;

另外,这种常量上下文并不会直接将表达式转换为完全不可变的。例如,如果某个属性又是一个数组,那么,这个属性数组并不会转变为不可变类型。

let arr = [1, 2, 3, 4];
let foo = {
  name: 'foo',
  contents: arr,
} as const;
// 错误!
foo.name = 'bar';
// 错误!
foo.contents = [];
foo.contents.push(5); // 成功!

这些例子来自官方说明,具体细节可以查看具体推送

总结

在上面的METHOD_MAP的例子中,const断言并不是必须的。我们也可以使用枚举:

设计枚举的目的就是描述常量,给常量一个名字。所以,枚举的成员就是只读的。因此,字符串枚举的成员就是字符串字面量类型。某些开发人员不喜欢使用枚举,因为枚举不是传统的 JavaScript。此时,const断言可以作为一个替代品。

const断言允许我们编写更少的类型声明。如果你使用 Redux,const断言能够极大地减少 Action、Reducer 以及类型守卫。例如:

let test: {
  readonly a: 'a';
  readonly b: {
    readonly name: 1;
  };
  readonly c: readonly [true, false];
} = {
  a: 'a',
  b: { name: 1 },
  c: [true, false],
};

发表评论

关于我

devbean

devbean

豆子,生于山东,定居南京。毕业于山东大学软件工程专业。软件工程师,主要关注于 Qt、Angular 等界面技术。

主题 Salodad 由 PenciDesign 提供 | 静态文件存储由又拍云存储提供 | 苏ICP备13027999号-2