天天看點

TypeScript 中的 any、unknown、never 和 void

作者:前端西瓜哥

大家好,我是前端西瓜哥。今天來學習 TS 中幾個比較特殊的類型:any、never、never、void。

any

any 表示 任意類型。

它是任意類型的父類,任意類型的值都可以賦予給 any 類型:

// 編譯不會報錯
let anything: any = '前端西瓜哥';

let flag: boolean = true;
anything = flag;

anything = { num: 2 };           

它可以表示任何類型,并使用它們的文法,就像寫沒有類型的原生 JS 一樣。

// 編譯不會報錯
const a: any = 6;
a();
a.key1 = true;           

any 相當于抛棄了類型系統,會讓代碼變得不可預測和難以維護,需要程式員小心維護,一有不慎會造成運作時的錯誤,是以盡量少用。

但在引入一些沒有提供類型的第三方純 JS 庫時,還是得将它們标為 any,這個确實沒啥辦法。

除非你自己給第三方庫寫類型聲明,但太不現實,因為你不熟悉第三方庫的 API,且可能有很複雜的類型推導要實作。

any 的存在是為了相容無類型的 JS。TS 作為 JS 的超集,用 any 開後門是不得不做的事情。

unknown

unknown 可以認為是 類型更安全的 any。

和 any 一樣,unknown 也是任何類型的子類型,所有類型都可以傳給 unknown,包括 any。

// 編譯不會報錯
let a: unknown = '前端西瓜哥';
let b: any;
a = b;
a = { num: 2 };           

說 unknown 更安全,是因為 unknown 是不能進行任何操作的。如果要使用,需要用 as 來進行顯式的類型斷言。

declare const user: known;
// 報錯,unknown 不能被使用
user.toLowerCase();
// 開發者認為 user 是個字元串
// 使用 as 進行類型推斷才能使用
(user as string).toLowerCase();           

或者我們可以用類型收窄(Type Narrowing);

declare const user: unknown;
if (typeof user === 'string') {
  user.toLowerCase();
}           

對于一些可疑的沒有類型的變量,如果你不希望它被不小心使用,此時就可以用 unknown。

總結就是:unknown 能看不能用,想用先類型斷言。

never

never 表示一個 無法被觀測的類型,被賦予了該類型的變量什麼都不能做。

使用 never 的一些場景。

(1)一個無法走到 return 傳回值的函數,比如一定會抛出錯誤或死循環:

// 這裡的 never 表示無法執行到函數傳回它的傳回值
function foo(): never {
  throw new Error('something wrong!')
}           

(2)TS 在判斷條件下會做類型的收窄,當類型收縮到無類型可用,類型就變成了 never:

function getData(id: number | string) {
  if (typeof id === 'string') {
    // id 類型變成了 string
  } else if (typeof id === 'number') {
    // id 類型變成了 number
  } else {
    // id 類型變成了 never
  }
}           

或者做了一種不可能為 true 的類型收窄:

if (typeof id === 'number' && typeof id === 'string') {
  // id 不可能同時是 number 和 string
  // 是以會變成 never
}           

或無法求出交集的交叉類型:

// ImpossibleType 會得到 never
type ImpossibleType = string & number;           

(3)類型程式設計中,在做模式比對時,如果比對失敗,還是要傳回一個類型的。為了表示失敗,傳回一個 never 表示傳回的類型是無法被使用的。

比如 TS 内置的 Parameters 進階類型,會通過模式比對提取函數的參數數組類型。如果無法比對到參數,會傳回 never:

type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never           

(4)類型程式設計中,将一些類型丢棄。never 在聯合類型以及重映射的 key 中會被丢棄。

比如 TS 内置的 Exclude 進階類型,會将聯合類型 T 中的不屬于 U 的 key 丢棄掉。

type Exclude<T, U> = T extends U ? never : T
type T = Exclude<"a" | "b" | "c", "a">;
// T 的類型為 "b" | "c"
// 其實應該是 never | "b" | "c",但 never 無意義,被丢掉了           

void

void 用于表示一個 函數沒有傳回值。

function sayHi(): void {
  console.log('Hi!');
}           

當然在實際 JS 運作時,還是會傳回一個預設的 undefined 的。但 TS 給傳回值設定為 void,語義更好些。

總結

any 是任意類型,具有所有類型的行為,可被執行,可通路屬性,超脫于類型系統之外。

unknown 則是類型更安全的 any,同樣可以将任何類型賦給它,但不能執行任何操作,必須用類型斷言來顯示說明類型才能去執行操作。

never 是無法觀測的類型,比如不會執行完的函數的傳回值,合并結果不存在的交叉類型。在類型程式設計中非常活躍,常用于丢棄一些子類型。

void 就比較簡單,隻是代表函數沒有傳回值,沒有其他的場景了。

我是前端西瓜哥,歡迎關注我,學習更多前端知識。

繼續閱讀