幾個一看就會的 TypeScript 小技巧
TypeScript 是一門語言,有很多語法,和那些只需要熟悉下 API 的庫的層次不太一樣,它更靈活,當然也會有很多小技巧。
這篇文章就來分享一些很多人不知道的小技巧吧,都是學完就能用起來的那種。
keyof any
TypeScript 有一個內置類型叫做 Record,它的作用是根據(jù)傳入的索引和值的類型構造新的索引類型。
它的實現(xiàn)就是通過映射類型的語法構造一個索引類型:
type Record<K, T> = { [P in K]: T };
那么問題來了,這個 K 怎么約束呢?
有同學說 K 不是索引么?那應該是 string,也就是 K extends string。
但是 JS 的屬性可以是 string、number、symbol 這三種類型的。
那我知道了,要 K extends string | number | symbol。
不不不,TypeScript 有個編譯選項叫做 keyofStringsOnly,開啟了那么就就只會用 string 作為索引,否則才是 string | number | symbol:
這還與編譯選項有關,那這里改怎么約束呢?
看下 TS 源碼里是怎么定義 Record 的:
type Record<K extends keyof any, T> = { [P in K]: T; };
它用了 keyof any,難道這個 keyof any 就能動態(tài)得到 key 支持的類型么?
我們試一下,不開啟 keyofStringsOnly 時:
開啟 keyofStringsOnly 時:
妙啊,這樣就能動態(tài)獲取當前支持的 key 的類型了。
需要約束某個類型參數(shù)為索引 Key 時,用 keyof any 動態(tài)獲取比寫死 string | number | symbol 更好。
object 和 Record<string, any>
object 和 RecordTypeScript 里有三個類型比較難區(qū)分,就是 object、Object、{} 這幾個。
其實只要記住 object 不能接受原始類型 就可以了,其余兩個差不多,只不過 {} 是個空對象,沒有索引。
所以 number 就可以賦值給 {}、Object 類型,但是不能賦值給 object 類型:
其實,你看源碼會發(fā)現(xiàn)大家不會用 object 來約束,而是用 Record 來約束索引類型,這倆其實是一樣的,但是 Record 更語義化一些。
Record 創(chuàng)建了一個 key 為任意 string,value 為任意類型的索引類型:
所以,平時約束索引類型的時候就可以用 Record 代替 object。
而且你會在很多源碼里看到這種寫法,比如下面是 Nest.js 源碼里的:
-readonly
映射類型可以構造一個新的索引類型,并且構造的過程中做一些修改。
比如構造一個新的索引類型,把所有的 Key 變?yōu)榭蛇x:
type ToPartial<T> = { [Key in keyof T]?: T[Key] }
或者構造一個新的索引類型,加上 readonly 的修飾:
type ToReadonly = { readonly [Key in keyof T]: T[Key]; }
但很多人不知道也可以去掉已有的修飾的,用 - 號,減去的意思:
比如去掉 ? 是 -? :
type ToRequired<T> = { [Key in keyof T]-?: T[Key] }
那去掉 readonly 自然就是 -readonly:
type ToMutable<T> = { -readonly [Key in keyof T]: T[Key] }
我最近看到 Promise.all 的類型定義就用到這個了:
類型參數(shù) T 是 待處理的 promise 數(shù)組,返回值是 Promise 的 value 對應的數(shù)組,用 Awaited 取出 value 的類型。
Awaited 是 TS 內置的一個高級類型,用于取出 Promise 返回值類型的:
返回的是數(shù)組類型,那為啥還可以用映射類型的語法呢?
因為數(shù)組類型也是索引類型呀,索引類型的意思就是聚合多個元素的類型,數(shù)組、對象、class 都是索引類型。
當然,主要還是為了講 -readonly 的語法,可以去掉 readonly 的修飾。
this
方法里可以調用 this,比如這樣:
class Dong {
name: string;
constructor() {
this.name = "dong";
}
hello() {
return 'hello, I\'m ' + this.name;
}
}
const dong = new Dong();
dong.hello();
用對象.方法名的方式調用的時候,this 就指向那個對象。
但是方法也可以用 call 或者 apply 調用:
call 調用的時候,this 就變了,但這里卻沒有被檢查出來 this 指向的錯誤。
如何讓編譯器能夠檢查出 this 指向的錯誤呢?
其實方法是可以指定 this 的類型的:
class Dong {
name: string;
constructor() {
this.name = "dong";
}
hello(this: Dong) {
return 'hello, I\'m ' + this.name;
}
}
這樣,當 call/apply 調用的時候,就能檢查出 this 指向的對象是否是對的:
而且,TypeScript 也提供了一個內置的高級類型 ThisParameterType 用于提取 this 的類型:
它的實現(xiàn)很簡單,就是通過模式匹配提取 this 的類型到 infer 聲明的局部變量里返回:
? 和 ??
最后是一個比較常用的語法,TS 支持 ? 的可選鏈語法,也可以通過 ?? 指定默認值:
const dong = data?.name ?? 'dong';
編譯之后會變成這樣:
做了空值檢查,也設置了默認值 dong。
很簡單和有用的一個語法,但很多人寫 ts 還是沒把它用起來。
總結
TypeScript 有很多靈活的語法,小技巧很多。
今天分享了一些大家可能不知道的技巧:
- keyof any 可以動態(tài)獲取 key 支持的類型,根據(jù) keyofStringsOnly 的編譯選項,可以用來約束索引。
- object 不能接收原始類型,而 {} 和 Object 都可以,這是它們的區(qū)別。
- object 一般會用 Record 代替,約束索引類型更加語義化。
- 映射類型語法可以創(chuàng)建索引類型,并且加上 readonly 或 ? 的修飾,其實也可以用 -readonly、-? 去掉。
- ? 和 ?? 分別代表空判斷和默認值,是寫 TS 很常用的一個語法。
- this 的類型是可以約束的,而且也可以用內置的高級類型 ThisParameterTypes 來取。
這幾個小技巧都是看一遍就會的那種,下次寫 TS 類型的時候就可以用起來了。