58. 精读《Typescript2.0 - 2.9》

1 引言

精读原文是 typescript 2.0-2.9 的文档:

2.0-2.8,2.9 草案.

我发现,许多写了一年以上 Typescript 开发者,对 Typescript 对理解和使用水平都停留在入门阶段。造成这个现象的原因是,Typescript 知识的积累需要 刻意练习,使用 Typescript 的时间与对它的了解程度几乎没有关系。

这篇文章精选了 TS 在 2.0-2.9 版本中最重要的功能,并配合实际案例解读,帮助你快速跟上 TS 的更新节奏。

对于 TS 内部优化的用户无感部分并不会罗列出来,因为这些优化都可在日常使用过程中感受到。

2 精读

由于 Typescript 在严格模式下的许多表现都与非严格模式不同,为了避免不必要的记忆,建议只记严格模式就好了!

严格模式导致的大量边界检测代码,已经有解了

直接访问一个变量的属性时,如果这个变量是 undefined,不但属性访问不到,js 还会抛出异常,这几乎是业务开发中最高频的报错了(往往是后端数据异常导致的),而 typescript 的 strict 模式会检查这种情况,不允许不安全的代码出现。

在 2.0 版本,提供了 “非空断言标志符” !. 解决明确不会报错的情况,比如配置文件是静态的,那肯定不会抛出异常,但在 2.0 之前的版本,我们可能要这么调用对象:

 
 

有了 2.0 提供的 “非空断言标志符”,我们可以这么写了:

 
 

在 2.8 版本,ts 支持了条件类型语法:

 
 

当 T 的类型是 string 时,TypeName 的表达式类型为 "string"。

这这时可以构造一个自动 “非空断言” 的类型,把代码简化为:

 
 

前提是框架先把 config 指定为这个特殊类型,这个特殊类型的定义如下:

 
 

也就是 2.8 的条件类型允许我们在类型判断进行递归,把所有对象的 key 都包一层 “非空断言”!

此处灵感来自 egg-ts 总结

增加了 never object 类型

当一个函数无法执行完,或者理解为中途中断时,TS 2.0 认为它是 never 类型。

比如 throw Error 或者 while(true) 都会导致函数返回值类型时 never

和 null undefined 特性一样,never 等于是函数返回值中的 null 或 undefined它们都是子类型,比如类型 number自带了 null 与 undefined 这两个子类型,是因为任何有类型的值都有可能是空(也就是执行期间可能没有值)。

这里涉及到很重要的概念,就是预定义了类型不代表类型一定如预期,就好比函数运行时可能因为 throw Error 而中断。所以 ts 为了处理这种情况,将 null undefined 设定为了所有类型的子类型,而从 2.0 开始,函数的返回值类型又多了一种子类型 never

TS 2.2 支持了 object 类型, 但许多时候我们总把 object 与 any 类型弄混淆,比如下面的代码:

 
 

这时候报错会出现,有时候闭个眼改成 any 就完事了。其实这时候只要把 object 删掉,换成 TS 的自动推导就搞定了。那么问题出在哪里?

首先 object 不是这么用的,它是 TS 2.3 版本中加入的,用来描述一种非基础类型,所以一般用在类型校验上,比如作为参数类型。如果参数类型是 object,那么允许任何对象数据传入,但不允许 3 "abc" 这种非对象类型:

 
 

而一开始 const persion: object 这种用法,是将能精确推导的对象类型,扩大到了整体的,模糊的对象类型,TS 自然无法推断这个对象拥有哪些 key,因为对象类型仅表示它是一个对象类型,在将对象作为整体观察时是成立的,但是 object类型是不承认任何具体的 key 的。

增加了修饰类型

TS 在 2.0 版本支持了 readonly 修饰符,被它修饰的变量无法被修改。

在 TS 2.8 版本,又增加了 - 与 + 修饰修饰符,有点像副词作用于形容词。举个例子,readonly 就是 +readonly,我们也可以使用 -readonly 移除只读的特性;也可以通过 -?: 的方式移除可选类型,因此可以延伸出一种新类型:Required<T>,将对象所有可选修饰移除,自然就成为了必选类型:

 
 

可以定义函数的 this 类型

也是 TS 2.0 版本中,我们可以定制 this 的类型,这个在 vue 框架中尤为有用:

 
 

this 类型是一种假参数,所以并不会影响函数真正参数数量与位置,只不过它定义在参数位置上,而且永远会插队在第一个。

引用、寻址支持通配符了

简单来说,就是模块名可以用 * 表示任何单词了:

 
 

它的类型可以辐射到:

 
 

这个特性很强大的一个点是用在拓展模块上,因为包括 tsconfig.json 的模块查找也支持通配符了!举个例子一下就懂:

最近比较火的 umi 框架,它有一个 locale 插件,只要安装了这个插件,就可以从 umi/locale 获取国际化内容:

 
 

其实它的实现是创建了一个文件,通过 webpack.alias 将引用指了过去。这个做法非常棒,那么如何为它加上类型支持呢?只要这么配置 tsconfig.json:

 
 

将所有 umi/* 的类型都指向 <somePath>,那么 umi/locale 就会指向 <somePath>/locale.ts 这个文件,如果插件自动创建的文件名也恰好叫 locale.ts,那么类型就自动对应上了。

跳过仓库类型报错

TS 在 2.x 支持了许多新 compileOptions,但 skipLibCheck 实在是太耀眼了,笔者必须单独提出来说。

skipLibCheck 这个属性不但可以忽略 npm 不规范带来的报错,还能最大限度的支持类型系统,可谓一举两得。

拿某 UI 库举例,某天发布的小版本 d.ts 文件出现一个漏洞,导致整个项目构建失败,你不再需要提 PR 催促作者修复了!skipLibCheck 可以忽略这种报错,同时还能保持类型的自动推导,也就是说这比 declare module "ui-lib" 将类型设置为 any 更强大。

对类型修饰的增强

TS 2.1 版本可谓是针对类型操作革命性的版本,我们可以通过 keyof 拿到对象 key 的类型:

 
 

基于 keyof,我们可以增强对象的类型:

 
 

Tips:在 TS 2.8 版本,我们可以以表达式作为 keyof 的参数,比如 keyof (A & B)。Tips:在 TS 2.9 版本,keyof可能返回非 string 类型的值,因此从一开始就不要认为 keyof 的返回类型一定是 string

NewObjType 原封不动的将对象类型重新描述了一遍,这看上去没什么意义。但实际上我们有三处拓展的地方:

  • 左边:比如可以通过 readonly 修饰,将对象的属性变成只读。

  • 中间:比如将 : 改成 ?:,将对象所有属性变成可选。

  • 右边:比如套一层 Promise<T[P]>,将对象每个 key 的 value 类型覆盖。

基于这些能力,我们拓展出一系列上层很有用的 interface

  • Readonly。把对象 key 全部设置为只读,或者利用 2.8 的条件类型语法,实现递归设置只读。

  • Partial。把对象的 key 都设置为可选。

  • Pick<T, K>。从对象类型 T 挑选一些属性 K,比如对象拥有 10 个 key,只需要将 K 设置为 "name" | "age" 就可以生成仅支持这两个 key 的新对象类型。

  • Extract<T, U>。是 Pick 的底层 API,直到 2.8 版本才内置进来,可以认为 Pick 是挑选对象的某些 key,Extract 是挑选 key 中的 key。

  • Record<K, U>。将对象某些属性转换成另一个类型。比较常见用在回调场景,回调函数返回的类型会覆盖对象每一个 key 的类型,此时类型系统需要 Record 接口才能完成推导。

  • Exclude<T, U>。将 T 中的 U 类型排除,和 Extract 功能相反。

  • Omit<T, K>(未内置)。从对象 T 中排除 key 是 K 的属性。可以利用内置类型方便推导出来:type Omit<T, K> = Pick<T, Exclude<keyof T, K>>

  • NonNullable。排除 T 的 null 与 undefined 的可能性。

  • ReturnType。获取函数 T 返回值的类型,这个类型意义很大。

  • InstanceType。获取一个构造函数类型的实例类型。

以上类型都内置在 lib.d.ts 中,不需要定义就可直接使用,可以认为是 Typescript 的 utils 工具库。

单独拿 ReturnType 举个例子,体现出其重要性:

Redux 的 Connect 第一个参数是 mapStateToProps,这些 Props 会自动与 React Props 聚合,我们可以利用 ReturnType<typeof currentMapStateToProps> 拿到当前 Connect 注入给 Props 的类型,就可以打通 Connect 与 React 组件的类型系统了。

对 Generators 和 async/await 的类型定义

TS 2.3 版本做了许多对 Generators 的增强,但实际上我们早已用 async/await 替代了它,所以 TS 对 Generators 的增强可以忽略。需要注意的一块是对 for..of 语法的异步迭代支持:

 
 

这可以对每一步进行异步迭代。注意对比下面的写法:

 
 

对于 fn1,它的返回值是可迭代的对象,并且每个 item 类型都是 Promise 或者 Generator。对于 fn2,它自身是个异步函数,返回值是可迭代的,而且每个 item 都不是异步的。举个例子:

 
 

在这里顺带一提,对 Array.map 的每一项进行异步等待的方法:

 
 

如果为了执行顺序,可以换成 for..of 的语法,因为数组类型是一种可迭代类型。

泛型默认参数

了解这个之前,先介绍一下 TS 2.0 之前就支持的函数类型重载。

首先 JS 是不支持方法重载的,Java 是支持的,而 TS 类型系统一定程度在对标 Java,当然要支持这个功能。好在 JS 有一些偏方实现伪方法重载,典型的是 redux 的 createStore

 
 

既然 JS 有办法支持方法重载,那 TS 补充了函数类型重载,两者结合就等于 Java 方法重载:

 
 

可以清晰的看到,createStore 想表现的是对参数个数的重载,如果定义了函数类型重载,TS 会根据函数类型自动判断对应的是哪个定义。

而在 TS 2.3 版本支持了泛型默认参数,可以某些场景减少函数类型重载的代码量,比如对于下面的代码:

 
 

通过枚举表达了范型默认值,以及 U 与 T 之间可能存在的关系,这些都可以用泛型默认参数解决:

 
 

尤其在 React 使用过程中,如果用泛型默认值定义了 Component

 
 

就可以实现以下等价的效果:

 
 

动态 Import

TS 从 2.4 版本开始支持了动态 Import,同时 Webpack4.0 也支持了这个语法(在 精读《webpack4.0%20 升级指南》 有详细介绍),这个语法就正式可以用于生产环境了:

 
 

准确的说,动态 Import 实现于 webpack 2.1.0-beta.28,最终在 TS 2.4 版本获得了语法支持。

在 TS 2.9 版本开始,支持了 import() 类型定义:

 
 

也就是 typeof 可以作用于 import() 语法,而不真正引入 js 内容。不过要注意的是,这个 import('./utils/create-zip-file') 路径需要可被推导,比如要存在这个 npm 模块、相对路径、或者在 tsconfig.json 定义了 paths

好在 import 语法本身限制了路径必须是字面量,使得自动推导的成功率非常高,只要是正确的代码几乎一定可以推导出来。好吧,所以这也从另一个角度推荐大家放弃 require

Enum 类型支持字符串

从 Typescript 2.4 开始,支持了枚举类型使用字符串做为 value:

 
 

笔者在这提醒一句,这个功能在纯前端代码内可能没有用。因为在 TS 中所有 enum 的地方都建议使用 enum 接收,下面给出例子:

 
 

不仅是可读性,enum 对应的数字可能会改变,直接写 75 的做法存在风险。

但如果前后端存在交互,前端是不可能发送 enum 对象的,必须要转化成数字,这时使用字符串作为 value 会更安全:

 
 

数组类型可以明确长度

最典型的是 chart 图,经常是这样的二维数组数据类型:

 
 

一般我们会这么描述其数据结构:

 
 

在 TS 2.7 版本中,我们可以更精确的描述每一项的类型与数组总长度:

 
 

自动类型推导

自动类型推导有两种,分别是 typeof:

 
 

和 instanceof:

 
 

在 TS 2.7 版本中,新增了 in 的推导:

 
 

这个解决了 object 类型的自动推导问题,因为 object 既无法用 keyof 也无法用 instanceof 判定类型,因此找到对象的特征吧,再也不要用 as 了:

4 总结

Typescript 2.0-2.9 文档整体读下来,可以看出还是有较强连贯性的。但我们可能并不习惯一步步学习新语法,因为新语法需要时间消化、同时要连接到以往语法的上下文才能更好理解,所以本文从功能角度,而非版本角度梳理了 TS 的新特性,比较符合学习习惯。

另一个感悟是,我们也许要用追月刊漫画的思维去学习新语言,特别是 TS 这种正在发展中,并且迭代速度很快的语言。

5 更多讨论

讨论地址是:精读《Typescript2.0 - 2.9》 · Issue #85 · dt-fe/weekly

如果你想参与讨论,请点击这里,每周都有新的主题,周末或周一发布。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值