疯狂的技术宅 前端先锋 前端
每日前端夜话0x6F
每日前端夜话,陪你聊前端。
天天晚上18:00准时推送。
正文共:1916 字
预计阅读时间: 6 分钟
翻译:疯狂的技术宅
来源:logrockettypescript
我发现官方的 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