大家好,我是若川。持续组织了6个月源码共读活动,感兴趣的可以点此加我微信 ruochuan12 参与,每周大家一起学习200行左右的源码,共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列
上周分享了 ts 入门以及常用的小技巧《TS 在项目中的 N 个实用小技巧 - 文字稿》,然后发现有好几个关注了公众号来提问的(结果我开了小冰的自动回复,有了以下奇怪的对话

当看到想要回复的时候,发现...(怪我没有开通知,略微尴尬 ~~ 😷

那就只能发文来讲一讲了(希望对应的小伙伴能看到)。回顾下之前的题目,问怎么能让他的返回结果是 string
const user = {name: 'amy', age: 18}
function getPersonInfo(key: keyof typeof user) {return user[key];
}const userName = getPersonInfo('name'); // string | number解决方法肯定是泛型。这里需要关注的一个点是,泛型是可以只定义传参不传的,例如下文的 T extends keyof UserType 就限定了 T 一定是 user 中的一个 key,这个时候将参数的类型定义定义成 T ,那么返回类型自然是 UserType[T] 了,这里返回类型不定义也成,ts 能根据下面的写法自动推断出来。这种通过泛型结合 extends 的写法,可以一定情况下来辅助指定参数的类型。
const user = {name: 'amy', age: 18}
type UserType =  typeof userfunction getPersonInfo<T extends keyof UserType>(key: T): UserType[T] {return user[key];
}const userName = getPersonInfo('name'); // string专门开了篇文章,那肯定得写点什么。上一篇文章做 ts 的分享的时候,发现市面上大多文章都是基于 ts3.x 的版本去做分析讲解的,就算高级进阶篇,很多也只是讲到一些常用的工具函数就戛然而至。甚至 google 搜索到的位于搜索引擎的 ts 中文网(据接触,有不少有中文文档就看中文文档的小伙伴),也只是更新到 3.1,从 npm 的发布记录来看也是四年前的版本了。所以,来写点大家说得相对少的,但是我们日常也可以感知到新玩意吧~~
正文开始

几个对 ECMAScript 提案的跟进
一、可选操作链和空值合并
在 es2020 中,新增了一个深受大家喜爱的属性:proposal-optional-chaining ,通常来说我们要使用需要结合 babel 配合转译来兼容低版本的浏览器(使用 @babel/preset-env  的 ES2020 版本,或者使用 @babel/plugin-proposal-optional-chaining 插件。
另外还有一个运算符 ?? 空值合并(nullish coalescing operator)。例如 a ?? b 可以等同于 (a !== null && a !== undefined) ? a : b; 这个在做一些数据接口校验的时候有些作用。
在 typescript 3.7 版本中,针对上述说的两种操作符都及时做了支持,也就意味着如果你只使用了 typescript ,而没使用 babel  的场景也可以用它来玩(场景不多见,知道有这么回事就好)
let x = foo?.bar.baz();
// => let x = foo === null || foo === void 0 ? void 0 : foo.bar.baz();let x = foo ?? bar();
// let x = foo !== null && foo !== void 0 ? foo : bar();二、私有字段(Private Fields)
在 typescirpt 3.8 中支持使用 # 表示私有字段,另外在 4.3 版本中也支持了方法和 getter 都能有类似的写法。demo 如下:
class Person {#name: stringconstructor(name: string) {this.#name = name;}#someMethod() {//...}get #someValue() {return 100;}
}let person = new Person('Amy');
person.#name
// Property '#name' is not accessible outside class 'Person'其实跟 private 关键词差不多,不同的点在于上述代码,如果使用的是 private 属性,你通过 person['name'] 这种手段还是可以访问到,而你使用 person['#name'] 依然会报错
三、短路运算符
三个运算符新增 *= 的操作:&&= 、 ||=  和  ??=
跟常见的 let a += a+1 的感觉差不多,只不过这里的操作运算符是 && || 和 ?? 而已,一个实际 demo,下面三种写法都是一个意思。
const a = { duration: 50, title: '' };a.duration ||= 10; // demo1, => 50a.duration = a.duration || 10; // demo2, => 50if (!a.duration) { // demo3, => 50a.duration = 10; 
}语言特性的优化
一、Type-Only Imports and Export
翻译过来,就是仅仅导入导出 type 。有一些用 ts 的小伙伴可能经常会看到一些warning 提示,找不到 xx 定义。但是点进文件一看,那些定义都好端端的写在文件中,于是一头雾水甚至直接忽略 warning 提示了。

这其实是该功能会解决的一个问题,举一个例子来说明这情况产生的原因:
// types.ts
export type User = {... };
export type UserList = User[];// index.ts
export { User, UserList } from './types'; // ts types
export { getUser, CreateUser } from './user'; // js function从逻辑上看上面的代码并没有任何问题,但是在底层,这是一个被称之为「导入省略」的功能起的作用。通常 babel 在编译的时候,是一个个处理文件的,针对 ts 他一般是先删除类型,然后再进行编译。我们如果光看 index.ts ,实际都并不知道 User  和  CreateUser 谁是一个 ts type 的定义而谁是 js 运行时需要的东西。于是 babel 只能被迫的将所有东西都保留,于是转译后的文件为
// types.js
-- empty file --// index.js
export { User, UserList } from './types'; // ts types
export { getUser, CreateUser } from './user'; // js function而在 typescript 3.8 之后,我们的解决方法可以变成下述写法。针对 type 的导入或者导出,babel会在删除类型的环节,直接将 import type ... 或者 export type xxx 这类的语句直接去掉。
// index.ts
export type { User, UserList } from './types'; // ts types
export { getUser, CreateUser } from './user'; // js function// => babel 转换后
export { getUser, CreateUser } from './user'; // only js function另外,在 4.5 的版本中,支持了对于某个变量局部使用 type 的写法,就不用说类型和 js 的函数要拆成两条语句了
// ts 3.8
import type { BaseType } from "./some-module.js";
import { someFunc } from "./some-module.js";// => ts 4.5
import { someFunc, type BaseType } from "./some-module.js";二、模版字符串
跟 es6 的模版字符串类似,不过是用于类型。此外,用在模板字符串类型中的泛型或类型别名,类型必须满足是string | number | bigint | boolean | null | undefined之一(也就是基础类型)。
应该有不少小伙伴都听说过,知乎上 ts 体操也是慢慢的从这特性出来开始越来越火。实际应用个人觉得会更多对于一些需要字符串拼接的场景,减少枚举。这里简单的列举一下例子
2.1 字符串组合场景
以下是一个 antd 中的 tootoolTip 组件,他有 12 个方向,传统写法,我们可能会直接枚举 12 种,写起来有那么一丢丢累,而且还很容易手抖不小心拼错。

结合字符串模版和首字母大写的 Capitalize 方法,我们可以将纯枚举罗列,变成以下的组合:

2.2 跟 infer 结合解析路由参数
网上看到的,话不多说,直接上代码。将 :id 转成 {id: string},不是特别理解的可以复习复习 infer 然后多看几眼自己尝试写一写。

既然路由参数可以解析,那么 url 参数解析其实同理,想要将 a=1&b=2 转换成 { a:'1', b:'2' } 的话,自己撸的一个小思路:

另外还有一个在 map 中使用 as rename 的方法,结合模版语法,我们可以在写一些通用函数的时候偷偷懒
type Getters<T> = {[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
};interface Person {name: string;age: number;
}type PersonGetters = Getters<Person>
/* => {getName: () => string;getAge: () => number;
} */再有,通过字符串变量, lodash 的 get 方法也可以更加精准的定义,还有 vuex 的模版等等。在 TSconf2020 中 anders 也给了很多不错的例子在 github 上,有兴趣的可以自行查阅:https://github.com/ahejlsberg/tsconf2020-demos/blob/master/template/main.ts。总之,就很多很多可以玩的玩法可以探索的,只要愿意。

三、解构变量可以显式标记为未使用
使用解构的用法时,如果我们只需要第二个参数,而不需要第一个参数时,以前 ts 的语法检查总是会报错。在 typescript 4.2 版本之后,可以使用 _ 可以告诉 ts 这解构变量标记是未使用的,比如以下例子,只会报 second 没有被使用。
function getValues() {return ['a', 'b'];
}
const [_first, second] = getValues();
// 已声明“second”,但从未读取其值。一个注意事项,如果你使用了 typescript-eslint 那可能编辑器的 eslint 检查还是会提示错误,需要配置让 no-unused-vars 规则允许下划线的变量不被使用。
rules: {"@typescript-eslint/no-unused-vars": ["error", { "ignoreRestSiblings": true }]
},四、新增 Await 关键字
在 4.5 版本中支持,相当于可以快速获取 promise 的返回值了,结合 typeof 使用,或许可以节省几句对类型的 import。
const a = Promise.resolve('100')
// A = string
type A = Awaited<typeof a>;
// B = number
type B = Awaited<Promise<Promise<number>>>;
// C = boolean | number
type C = Awaited<boolean | Promise<number>>;最后
除了上述的一些描述,ts 每次更新当然也会有很多例如编译速度提升啊,更加符合 js 逻辑的一些自动推导的优化等等,这里就不做过多概述。还有一些配置项的新增,有兴趣的小伙伴可以自行查阅官方文档~~
相关回顾
1、TS 在项目中的 N 个实用小技巧 - 文字稿
生活总结
1、分享-前端小白的成长历程文字稿
2、2021 总结 | 鳗鱼 - 平凡的生活
3、2020 总结 | 鳗鱼 - 一起来吃鳗鱼饭吧
4、2019 总结 | 鳗鱼 - 写在 24 岁门口的自己

················· 若川简介 ·················
你好,我是若川,毕业于江西高校。现在是一名前端开发“工程师”。写有《学习源码整体架构系列》20余篇,在知乎、掘金收获超百万阅读。
从2014年起,每年都会写一篇年度总结,已经坚持写了8年,点击查看年度总结。
同时,最近组织了源码共读活动,帮助3000+前端人学会看源码。公众号愿景:帮助5年内前端人走向前列。

扫码加我微信 ruochuan02、拉你进源码共读群
今日话题
略。分享、收藏、点赞、在看我的文章就是对我最大的支持~