大家好,我是若川。持续组织了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 user
function 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: string
constructor(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, => 50
a.duration = a.duration || 10; // demo2, => 50
if (!a.duration) { // demo3, => 50
a.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 逻辑的一些自动推导的优化等等,这里就不做过多概述。还有一些配置项的新增,有兴趣的小伙伴可以自行查阅官方文档~~
················· 若川简介 ·················
你好,我是若川,毕业于江西高校。现在是一名前端开发“工程师”。写有《学习源码整体架构系列》20余篇,在知乎、掘金收获超百万阅读。
从2014年起,每年都会写一篇年度总结,已经坚持写了8年,点击查看年度总结。
同时,最近组织了源码共读活动,帮助3000+前端人学会看源码。公众号愿景:帮助5年内前端人走向前列。
扫码加我微信 ruochuan02、拉你进源码共读群
文章评论