杀手级的TypeScript功能:const断言[每日前端夜话0x6F]

杀手级的TypeScript功能:const断言[每日前端夜话0x6F]

疯狂的技术宅 前端先锋 前端

每日前端夜话0x6F
每日前端夜话,陪你聊前端。
天天晚上18:00准时推送。
正文共:1916 字
预计阅读时间: 6 分钟
翻译:疯狂的技术宅
来源:logrockettypescript

杀手级的TypeScript功能:const断言[每日前端夜话0x6F]
我发现官方的 TypeScript 文档很是有用,可是总以为有点过于学术化而且枯燥无味。每当我发现一个新功能时,我想要知道这个功能究竟可以解决什么问题而不是长篇大论。redux

在我看来,const assertions 是 TypeScript 3.4 的杀手级新功能,正如我稍后将要解释的,咱们能够用这个新功能省略不少繁琐的类型声明。数组

const 断言安全


1const x = { text: "hello" } as const;

官方文档中给出了这样的解释:ide

TypeScript 3.4 引入了一个名为 const 断言的字面值的新构造。它的语法是一个类型断言,用 const 代替类型名称(例如 123 as const)断言构造新的文字表达式时,咱们能够向语言发出如下信号:
该表达式中的字面类型不该被扩展(例如:不能从“hello”转换为字符串)
对象字面量获取只读属性
数组文字成为只读元组函数

感受有点枯燥,还有点混乱。让咱们来各个击破。翻译

没有类型扩展的字面类型


并非每一个人都知道类型扩展,而且因为某些意外行为而首次发现它时都会以为意外。code

当咱们使用关键字 const 声明一个字面量时,类型是等号右边的文字,例如:对象

1const x = 'x'; // x has the type 'x'

const 关键字确保不会发生对变量进行从新分配,而且只保证该字面量的严格类型。

可是若是咱们用 let 而不是 const, 那么该变量会被从新分配,而且类型会被扩展为字符串类型,以下所示:

1let x = 'x'; // x has the type string;

如下是两个不一样的声明:

1const x = 'x'; // has the type 'x' 
2let y = 'x';   // has the type string

y 被扩展为更通用的类型,并容许将其从新分配给该类型的其余值,而变量 x 只能具备 'x'的值。

用新的 const 功能,我能够这样作:

1let y = 'x' as const; // y has type 'x'`

对象字面量获取只读属性


在 Typescript 3.4 以前,类型扩展发生在对象字面量中:

1const action = { type: 'INCREMENT', } // has type { type: string }

即便咱们将 action 声明为 const,仍然能够从新分配 type 属性,所以,该属性被扩展成了字符串类型。

这看上去使人以为不是那么有用,因此让咱们换一个更好的例子。

若是你熟悉 Redux,就可能会发现上面的 action 变量能够用做 Redux action。若是你不知道 Redux 我来简单解释一下,Redux 是一个全局不可变的 state 存储。经过向所谓的 reducers 发送动做来修改状态。 reducers 是纯函数,它在调度每一个 action 后返回全局状态的新更新版本,以反映 acion 中指定的修改。

在 Redux 中,标准作法是从名为 action creators 的函数建立操做。 action creators 只是纯函数,它返回 Redux操做对象字面量以及提供给函数的全部参数。

用一个例子能够更好地说明这一点。应用程序可能须要一个全局 count 属性,为了更新这个 count 属性,咱们能够调度类型为 'SET_COUNT' 的动做,它只是将全局 count 属性设置为一个新的值,这是一个字面对象属性。这个 action 的 action creator 将是一个函数,它接受一个数字做为参数,并返回一个具备属性为 type、值为 SET_COUNT 和类型为 number 的 payload 属性的对象,它将指定 count 的新值:

1const setCount = (n: number) => {
 2  return {
 3    type: 'SET_COUNT',
 4    payload: n,
 5  }
 6}
 7
 8const action = setCount(3)
 9// action has type
10// { type: string, payload: number }

从上面显示的代码中能够看出,type 属性已经被扩展为 string 类型而再也不是 SET_COUNT。这不是很安全的类型,咱们能够保证的是 type 是一个字符串。 redux 中的每一个 action 都有一个 type 属性,它是一个字符串。

这不是很好,若是咱们想要利用 type 属性上的可区分联合的话,那么在 TypeScript 3.4 以前,则须要为每一个 action 声明一个接口或类型:

1interface SetCount {
 2  type: 'SET_COUNT';
 3  payload: number;
 4}
 5
 6const setCount = (n: number): SetCount => {
 7  return {
 8    type: 'SET_COUNT',
 9    payload: n,
10  }
11}
12
13const action = setCount(3)
14// action has type SetCount

这确实增长了编写 Redux action 和 reducers 的负担,但咱们能够经过添加一个 const assertion 来解决这个问题:

1const setCount = (n: number) => {
 2  return <const>{
 3    type: 'SET_COUNT',
 4    payload: n
 5  }
 6}
 7
 8const action = setCount(3);
 9// action has type
10//  { readonly type: "SET_COUNT"; readonly payload: number };

你会注意到从 setCount 推断的类型已经在每一个属性中附加了 readonly 修饰符,正如文档的项目符号所述。

这就是所发生的事情:

1{
2  readonly type: "SET_COUNT";
3  readonly payload: number
4};

action 中的每一个字面量都被添加了 readonly 修饰符。

在 redux 中,咱们建立了一个接受 action 的联合,reducer 函数能够经过这种操做来得到良好的类型安全性。在 TypeScript 3.4 以前,咱们会这样作:

1interface SetCount {
 2  type: 'SET_COUNT';
 3  payload: number;
 4}
 5
 6interface ResetCount {
 7  type: 'RESET_COUNT';
 8}
 9
10const setCount = (n: number): SetCount => {
11  return {
12    type: 'SET_COUNT',
13    payload: n,
14  }
15}
16
17const resetCount = (): ResetCount => {
18  return {
19    type: 'RESET_COUNT',
20  }
21}
22
23type CountActions = SetCount | ResetCount

咱们建立了两个接口 RESET_COUNT 和 SET_COUNT 来对两个 resetCount 和 setCount 的返回类型进行归类。

CountActions 是这两个接口的联合。

使用 const assertions,咱们能够经过使用 const、 ReturnType 和 typeof 的组合来消除声明这些接口的须要:

1const setCount = (n: number) => {
 2  return <const>{
 3    type: 'SET_COUNT',
 4    payload: n
 5  }
 6}
 7
 8const resetCount = () => {
 9  return <const>{
10    type: 'RESET_COUNT'
11  }
12}
13
14type CountActions = ReturnType<typeof setCount> | ReturnType<typeof resetCount>;

咱们从 action creator 函数 setCount 和 resetCount 的返回类型中推断出一个很好的 action 联合。

数组字面量成为只读元组


在 TypeScript 3.4 以前,声明一个字面量数组将被扩展而且能够修改。

使用 const,咱们能够将字面量锁定为其显式值,也不容许修改。

若是咱们有一个用于设置小时数组的 redux action 类型,它可能看起来像这样:

1const action = {
2  type: 'SET_HOURS',
3  payload: [8, 12, 5, 8],
4}
5//  { type: string; payload: number[]; }
6
7action.payload.push(12) // no error

在 TypeScript 3.4 以前,扩展会使上述操做的字面量属性更加通用,由于它们是能够修改的。

若是咱们将 const 应用于对象字面量,那么就能够很好地控制全部内容:

1const action = <const>{
 2  type: 'SET_HOURS',
 3  payload: [8, 12, 5, 8]
 4}
 5
 6// {
 7//  readonly type: "SET_HOURS";
 8//  readonly payload: readonly [8, 12, 5, 8];
 9// }
10
11action.payload.push(12);  // error - Property 'push' does not exist on type 'readonly [8, 12, 5, 8]'.

这里发生的事情偏偏是文档的要点:

payload 数组确实是 [8,12,5,8] 的“只读”元组(不过我并无从文档中看到这方面的说明)。

结论


我用如下代码总结以上全部内容:

1let obj = {
2  x: 10,
3  y: [20, 30],
4  z: {
5    a:
6      {  b: 42 }
7  } 
8} as const;

对应于:

1let obj: {
2  readonly x: 10;
3  readonly y: readonly [20, 30];
4  readonly z: {
5    readonly a: {
6      readonly b: 42;
7    };
8  };
9};

在这里,我能够推断出类型,而不是去编写多余的样板类型。这对于 redux 特别有用。

原文:https://blog.logrocket.com/const-assertions-are-the-killer-new-typescript-feature-b73451f35802

相关文章
相关标签/搜索