大家好,我是若川。持續組織了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、拉你進源碼共讀群
今日話題
略。分享、收藏、點贊、在看我的文章就是對我最大的支持~