TypeScript 非空斷言
發布于?2020-04-08 15:20:15
17.5K0
舉報
一、非空斷言有啥用
介紹非空斷言前,先來看個示例:
function sayHello(name: string | undefined) {let sname: string = name; // Error
}
對于以上代碼,TypeScript 編譯器會提示一下錯誤信息:
Type 'string | undefined' is not assignable to type 'string'.
Type 'undefined' is not assignable to type 'string'.
要解決上述問題,我們可以簡單加個條件判斷:
function sayHello(name: string | undefined) {let sname: string;if (name) {sname = name;}
}
使用這種方案,問題是解決了。但有沒有更簡單的方式呢?答案是有的,就是使用 TypeScript 2.0 提供的非空斷言操作符:
function sayHello(name: string | undefined) {let sname: string = name!;
}
二、非空斷言操作符簡介
在上下文中當類型檢查器無法斷定類型時,一個新的后綴表達式操作符 !
可以用于斷言操作對象是非 null 和非undefined 類型。具體而言,x!
將從 x 值域中排除 null
和 undefined
。
下面我們來介紹一下非空斷言操作符的一些使用場景和注意事項。
2.1 忽略 undefined 和 null 類型
function myFunc(maybeString: string | undefined | null) {// Type 'string | null | undefined' is not assignable to type 'string'.// Type 'undefined' is not assignable to type 'string'. const onlyString: string = maybeString; // Errorconst ignoreUndefinedAndNull: string = maybeString!; // Ok
}
2.2 調用函數時忽略 undefined 類型
type NumGenerator = () => number;function myFunc(numGenerator: NumGenerator | undefined) {// Object is possibly 'undefined'. // Cannot invoke an object which is possibly 'undefined'.const num1 = numGenerator(); // Errorconst num2 = numGenerator!(); //OK
}
2.3 使用非空斷言操作符的注意事項
因為 !
非空斷言操作符會從編譯生成的 JavaScript 代碼中移除,所以在實際使用的過程中,要特別注意。
下面我們來舉兩個簡單的示例:
示例一
const a: number | undefined = undefined;
const b: number = a!;
console.log(b);
以上 TS 代碼會編譯生成以下 ES5 代碼:
"use strict";
const a = undefined;
const b = a;
console.log(b);
雖然在 TS 代碼中,我們使用了非空斷言,使得 const b: number = a!;
語句可以通過 TypeScript 類型檢查器的檢查。但在生成的 ES5 代碼中,!
非空斷言操作符被移除了,所以在瀏覽器中執行以上代碼,在控制臺會輸出 undefined
。
示例二
type NumGenerator = () => number;function myFunc(numGenerator: NumGenerator | undefined) {const num1 = numGenerator!();
}// Uncaught TypeError: numGenerator is not a function
myFunc(undefined); // Error
以上 TS 代碼會編譯生成以下 ES5 代碼:
"use strict";
function myFunc(numGenerator) {var num1 = numGenerator();
}// Uncaught TypeError: numGenerator is not a function
myFunc(undefined); // Error
若在瀏覽器中運行以上代碼,在控制臺會輸出以下錯誤信息:
Uncaught TypeError: numGenerator is not a functionat myFunc (eval at <anonymous> (main-3.js:1239), <anonymous>:3:16)at eval (eval at <anonymous> (main-3.js:1239), <anonymous>:6:1)at main-3.js:1239
很明顯在運行時,undefined 并不是函數對象,所以就不能正常調用。
需要注意的是,非空斷言操作符僅在啟用
strictNullChecks
標志的時候才生效。當關閉該標志時,編譯器不會檢查 undefined 類型和 null 類型的賦值。
三、非空斷言操作符使用示例
在以下示例中,首先我們使用 TypeScript 類型別名定義了一個 ListNode
類型,用于表示鏈表節點。該類型包含 data
和 next
兩個屬性,分別表示當前節點的值和下個節點。之后,我們還定義了以下兩個函數:
- addNext(node: ListNode):用于添加下一個節點;
- setNextValue(node: ListNode, value: number):用于設置下一個節點的值。
type ListNode = { data: number; next?: ListNode; };function addNext(node: ListNode) {if (node.next === undefined) {node.next = {data: 0};}
}function setNextValue(node: ListNode, value: number) {addNext(node);// (property) next?: ListNode | undefined// Object is possibly 'undefined'.(2532)node.next.data = value; // Error
}
對于以上代碼盡管我們知道在調用 addNext
方法后,node.next 屬性會被定義,但 TypeScript 在 node.next.data = value
這行代碼中并不能推斷出這些。這時候我們可以使用非空斷言運算符 !
來斷言 node.next 并不是 undefined,并且使編譯器警告無效:
function setNextValue(node: ListNode, value: number) {addNext(node);node.next!.data = value;
}
接著我們繼續看一個示例,假設你有一個表示 AJAX 請求過程的 UI 狀態。它要么處于初始狀態(initial),要么處于掛起狀態(pending),要么處于完成狀態(complete),要么處于錯誤狀態(error)。只有在完成狀態下才有響應,否則為 null。
type AjaxState<T> = {state: 'initial' | 'pending' | 'complete' | 'error';response: T | null;
}function getAjaxState( ajaxState: AjaxState<number[]> ) {if (ajaxState.state === 'complete') {// (property) response: number[] | null// Object is possibly 'null'.(2531)console.log(ajaxState.response.length); // Error}
}
雖然我們知道當請求的狀態為 complete
時,響應對象不會為 null,但 TypeScript 并無法感知這些,所以我們還需要使用非空斷言 ajaxState.response!.length
來忽略空值并使編譯器警告無效。對于這種場景,其實有一個更好的解決方案,即使用可辨識聯合:
type AjaxState<T> = { state: 'initial'|'pending'|'error', response: null } |{ state: 'complete', response: T };function getAjaxState( ajaxState: AjaxState<number[]> ) {if (ajaxState.state === 'complete') {console.log(ajaxState.response.length);}
}
通過引入可辨識聯合類型,我們把為 null 和非 null 的響應完美的區分開來,還避免了再次使用非空斷言,此外還大大提高了程序的可讀性。在 TypeScript 實際項目的開發過程中,除了使用非空斷言(!)之外,讀者還可以使用 TypeScript 3.7 版本中新引入的可選鏈運算符(?.)和空值合并運算符(??)來提高程序的可讀性。