大家好,我是 Echa。
11 月 1 日,TypeScript 4.9 發(fā)布了候選版本 (RC),直到穩(wěn)定版發(fā)布基本上不會(huì)有太大變化了,本次帶來(lái)的更新還是挺有意思的,下面我就跟大家來(lái)一起看一下~
新的 satisfies 操作符
在使用 TypeScript 類型推斷的時(shí)候,有很多情況下會(huì)讓我們面臨兩難的選擇:我們即希望確保某些表達(dá)式能夠匹配某些類型,但也希望保留這個(gè)表達(dá)式的特定類型用來(lái)類型推斷。
比如下面的例子,我們定義了一個(gè)顏色選擇對(duì)象:
const palette = { red: [255, 0, 0], green: "#00ff00", blue: [0, 0, 255] };
因?yàn)槊總€(gè)屬性都被賦予了默認(rèn)值,ts 會(huì)自動(dòng)幫我們自動(dòng)推導(dǎo) palette 的屬性類型,所以我們可以直接調(diào)用它們的方法:
// red 被推斷為 Number[] 類型 const a = palette.red.at(0); // green 被推斷為 string 類型 const b = palette.green.toUpperCase();
因?yàn)轭伾际枪潭ǖ模覀兿胱屛覀兊?nbsp;palette 對(duì)象擁有特定的幾個(gè)屬性,來(lái)避免我們寫出一些錯(cuò)別字:
const palette = { // 錯(cuò)別字:rad -> red rad: [255, 0, 0], green: "#00ff00", blue: [0, 0, 255] };
所以我們可能會(huì)為 palette 定義一個(gè)類型,這樣錯(cuò)別字就會(huì)被檢測(cè)出來(lái)了:
type Colors = "red" | "green" | "blue"; type RGB = [red: number, green: number, blue: number]; const palette: Record = { rad: [255, 0, 0], // ~~~~ The typo is now correctly detected green: "#00ff00", blue: [0, 0, 255] };
但是這時(shí)候我們?cè)僬{(diào)用 palette.red 的方法,你會(huì)發(fā)現(xiàn) TS 的類型推斷會(huì)出錯(cuò):
type Colors = "red" | "green" | "blue"; type RGB = [red: number, green: number, blue: number]; const palette: Record = { red: [255, 0, 0], green: "#00ff00", blue: [0, 0, 255] }; // 'palette.red' "could" 的類型是 string | RGB ,所以它不一定存在 at 方法 const a = palette.red.at(0);
這就讓我們陷入了兩難的境地,我們用更嚴(yán)格了類型約束了寫出 bug 的可能性,但是卻失去了類型推斷的能力。
satisfies 關(guān)鍵字就是用來(lái)解決這個(gè)問(wèn)題的,它既能讓我們驗(yàn)證表達(dá)式的類型是否與某個(gè)類型匹配,也可以保留基于值進(jìn)行類型推斷的能力:
type Colors = "red" | "green" | "blue"; type RGB = [red: number, green: number, blue: number]; const palette = { rad: [255, 0, 0], // 可以捕獲到錯(cuò)別字 rad green: "#00ff00", blue: [0, 0, 255] } satisfies Record; // 都可以調(diào)用 const a = palette.red.at(0); const b = palette.green.toUpperCase();
in 操作符類型收窄優(yōu)化
在日常開(kāi)發(fā)中,我們經(jīng)常需要處理一些在運(yùn)行時(shí)不完全確定的值,比如我們現(xiàn)在有下面兩個(gè)類型:
interface Duck { quack(): string; } interface Cat { miao(): string; }
在實(shí)際使用過(guò)程中,TS 不能確定 value 是否是上面中哪一個(gè)類型,所以會(huì)拋出錯(cuò)誤:
function main(value: Duck | Cat) { if (value.quack) { // roperty 'quack' does not exist on type 'Duck | Cat'. return value.quack; } }
我們可能會(huì)使用 in 這樣的關(guān)鍵字來(lái)實(shí)現(xiàn)簡(jiǎn)單的類型收窄:
function main(value: Duck | Cat) { if ('quack' in value) { return value.quack; } }
也可以實(shí)現(xiàn)一個(gè)更通用的類型守衛(wèi),可以參考我這篇文章:什么是鴨子類型?
但是,這個(gè)寫法的前提是我們用到的對(duì)象有明確的類型,如果這個(gè)對(duì)象的屬性沒(méi)有明確的類型呢?我們來(lái)看看下面這段 JAVAScript 代碼:
function tryGetPackageName(context) { const packageJSON = context.packageJSON; // 檢查是否是個(gè)對(duì)象 if (packageJSON && typeof packageJSON === "object") { // 檢查是否存在一個(gè)字符串類型的 name 屬性 if ("name" in packageJSON && typeof packageJSON.name === "string") { return packageJSON.name; } } return undefined; }
把這段代碼重寫為規(guī)范的 TypeScript,我們只需要定義一個(gè) Context 類型,但是由于 packageJSON 沒(méi)有明確的類型定義,再使用 in 進(jìn)行類型收窄就有問(wèn)題了:
interface Context { packageJSON: unknown; } function tryGetPackageName(context: Context) { const packageJSON = context.packageJSON; // 檢查是否是個(gè)對(duì)象 if (packageJSON && typeof packageJSON === "object") { // 檢查是否存在一個(gè)字符串類型的 name 屬性 if ("name" in packageJSON && typeof packageJSON.name === "string") { // ~~~~ // error! Property 'name' does not exist on type 'object. return packageJSON.name; // ~~~~ // error! Property 'name' does not exist on type 'object. } } return undefined; }
這是因?yàn)?nbsp;in 操作符只會(huì)嚴(yán)格收窄到實(shí)際定義被檢查屬性的類型,所以 packageJSON 的類型從 unknown 收窄到了 object ,而 object 類型上不存在 name 屬性,就會(huì)引發(fā)報(bào)錯(cuò)。
TypeScript 4.9 優(yōu)化了這個(gè)問(wèn)題,in 操作符更加強(qiáng)大了,它會(huì)被收窄為被檢查類型和 Record<"property-key-being-checked", unknown> 的交叉類型。。。
比如在上面的例子中,packageJSON 的類型會(huì)被收窄為 object & Record<"name",unknown>,這樣我們直接訪問(wèn) packageJSON.name 就沒(méi)問(wèn)題了!
interface Context { packageJSON: unknown; } function tryGetPackageName(context: Context): string | undefined { const packageJSON = context.packageJSON; // 檢查是否是個(gè)對(duì)象 if (packageJSON && typeof packageJSON === "object") { // 檢查是否存在一個(gè)字符串類型的 name 屬性 if ("name" in packageJSON && typeof packageJSON.name === "string") { // 可以正常運(yùn)行! return packageJSON.name; } } return undefined; }
TypeScript 4.9 還加強(qiáng)了一些關(guān)于如何使用 in 操作符的檢查,比如左側(cè)要檢查的屬性必須是 string | number | symbol 類型,而右側(cè)類型必須要可分配給 object。accessor 關(guān)鍵字支持
accessor 是 ECMAScript 中即將推出的一個(gè)類關(guān)鍵字,TypeScript 4.9 對(duì)它提供了支持:
class Person { accessor name: string; constructor(name: string) { this.name = name; } }
accessor 關(guān)鍵字可以為該屬性在運(yùn)行時(shí)轉(zhuǎn)換為一對(duì) get 和 set 訪問(wèn)私有支持字段的訪問(wèn)器:
class Person { #__name: string; get name() { return this.#__name; } set name(value: string) { this.#__name = name; } constructor(name: string) { this.name = name; } }
NaN 相等判斷警告
NaN 是一個(gè)特殊的數(shù)值,代表 “非數(shù)字” ,在 JS 中它和任何值相比較都是 false,包括它自己:
console.log(NaN == 0) // false console.log(NaN === 0) // false console.log(NaN == NaN) // false console.log(NaN === NaN) // false
相對(duì)應(yīng)的,所有值都不等于 NaN:
console.log(NaN != 0) // true console.log(NaN !== 0) // true console.log(NaN != NaN) // true console.log(NaN !== NaN) // true
這其實(shí)并不是 JavaScript 特有的問(wèn)題,因?yàn)槿魏伟?nbsp;IEEE-754 浮點(diǎn)數(shù)的語(yǔ)言都有相同的行為;但 JavaScript 的主要數(shù)字類型就是浮點(diǎn)數(shù),并且 JavaScript 中的數(shù)字解析為 NaN 還挺常見(jiàn)的,所以在代碼中去比較值是否等于 NaN 的情況還挺普遍的。但是正確的做法應(yīng)該是使用 Number.isNaN 函數(shù)來(lái)判斷。假如你不知道這個(gè)問(wèn)題,就可能引發(fā)一些 bug。
在 TypeScript 4.9 中,如果你直接用一些值和 NaN 相比較,會(huì)拋出錯(cuò)誤并提示你使用 Number.isNaN:
function validate(someValue: number) { return someValue !== NaN; // ~~~~~~~~~~~~~~~~~ // error: This condition will always return 'true'. // Did you mean '!Number.isNaN(someValue)'? }
return 關(guān)鍵字的定義
在編輯器中,當(dāng)你對(duì) return 關(guān)鍵字運(yùn)行 go-to-definition 時(shí),TypeScript 現(xiàn)在會(huì)自動(dòng)跳轉(zhuǎn)到相應(yīng)函數(shù)的頂部。這有助于我們快速了解 return 屬于哪個(gè)函數(shù)。
另外,TypeScript 會(huì)將此功能擴(kuò)展到更多關(guān)鍵字,例如 await、yield、switch、case、default 等等。
最后
TypeScript 團(tuán)隊(duì)最近還發(fā)布了 5.0 版本的迭代規(guī)劃(https://github.com/microsoft/TypeScript/issues/51362),這將是 TypeScript 的又一個(gè)大的版本,其中包含了很多有趣的想法,還是挺值得期待的!
更多詳細(xì)更新請(qǐng)查看 TypeScript 官方博客:https://devblogs.microsoft.com/typescript/announcing-typescript-4-9-rc/
你覺(jué)得上面哪些更新對(duì)你最有用呢?歡迎在評(píng)論區(qū)和我留言;如果這篇文章幫助到了你,歡迎點(diǎn)贊和關(guān)注。