TypeScript 你還只會用 any?

輕鬆學 TypeScript 系列教程

https://space.bilibili.com/406258607

在你剛學 TypeScript 的時候,是不是遇到了很多令人抓狂的問題,最終你用上 any 大招把問題解決了。如果後期你沒有系統的學習 TypeScript 的類型系統,你會發現你可能把 TypeScript 學成了 AnyScript。

在 TypeScript 中,any 類型被稱爲 top type。所謂的 top type 可以理解爲通用父類型,也就是能夠包含所有值的類型。

let value: any;

value = true; // OK
value = 42; // OK
value = "Hello World"; // OK
value = []; // OK
value = {}; // OK
value = Math.random; // OK
value = null; // OK
value = undefined; // OK
value = new TypeError(); // OK
value = Symbol("type"); // OK

而在 TypeScript 3.0 時,又引入一個新的 top type —— unknown 類型。同樣,你也可以把任何值賦給 unknown 類型的變量。

let value: unknown;

value = true; // OK
value = 42; // OK
value = "Hello World"; // OK
value = []; // OK
value = {}; // OK
value = Math.random; // OK
value = null; // OK
value = undefined; // OK
value = new TypeError(); // OK
value = Symbol("type"); // OK

那麼現在問題來了,any 類型和 unknown 類型之間有什麼區別呢?any 類型可以理解成我不在乎它的類型,而 unknown 類型可以理解成我不知道它的類型。

其實 any 類型本質上是類型系統的一個逃生艙口,TypeScript 允許我們對 any 類型的值執行任何操作,而無需事先執行任何形式的檢查

let value: any;

value.foo.bar; // OK
value.trim(); // OK
value(); // OK
new value(); // OK
value[0][1]; // OK

這會帶來什麼問題呢?下面我們來舉一個例子:

function invokeCallback(callback: any) {
    try {
        callback();
    } catch (err) {
        console.error(err)
    }
}

invokeCallback(1);

對於以上的 TS 代碼,在編譯期不會提示任何錯誤,但在運行期將會拋出運行時錯誤。作爲開發人員,any 類型給了我們很大的自由度,但同時也帶來了一些隱患。

爲了解決 any 類型存在的安全隱患,TypeScript 團隊在 3.0 版本時,引入了 unknown 類型,你可以把它理解成類型安全的 any 類型

那麼 unknown 類型是類型安全的體現在哪裏呢?這裏我們把 invokeCallback 函數參數的類型改爲 unknown 類型,之後 TS 編譯器就會提示相應的錯誤信息:

function invokeCallback(callback: unknown) {
    try {
        // Object is of type 'unknown'.(2571)
        callback(); // Error
    } catch (err) {
        console.error(err)
    }
}

invokeCallback(1);

相比 any 類型,TypeScript 會對 unknown 類型的變量執行類型檢查,從而避免出現 callback 參數非函數類型。要解決上述問題,我們需要縮小 callback 參數的類型,即可以通過 typeof 操作符來確保傳入的 callback 參數是函數類型的對象:

function invokeCallback(callback: unknown) {
    try {
        if (typeof callback === 'function') {
            callback();
        }
    } catch (err) {
        console.error(err)
    }
}

invokeCallback(1);

在實際工作中,你還可以通過 instanceof 或用戶自定義類型守衛等方式來縮窄變量的類型。

declare function isFunction(x: unknown): x is Function;

function f20(x: unknown) {
    if (x instanceof Error) {
        x;  // Error
    }
    if (isFunction(x)) {
        x;  // Function
    }
}

與 any 類型不同,因爲 TypeScript 會對 unknown 類型的變量執行類型檢查,所以當我們把之前代碼中 value 變量的類型改成 unknown 類型時,使用 value 變量的多個語句將出現錯誤。

let value: unknown;

// Object is of type 'unknown'.(2571)
value.foo.bar; // Error
value.trim(); // Error
value(); // Error
new value(); // Error
value[0][1]; // Error

另外,需要注意的是,unknown 類型的變量只能賦值給 any 類型和 unknown 類型本身。

let value: unknown;

let value1: unknown = value; // OK
let value2: any = value; // OK
let value3: boolean = value; // Error
let value4: number = value; // Error
let value5: string = value; // Error
let value6: object = value; // Error
let value7: any[] = value; // Error
let value8: Function = value; // Error

any 類型和 unknown 類型在這些場合中的表現也是不一樣的:

type T40 = keyof any;  // string | number | symbol
type T41 = keyof unknown;  // never

type T50<T> = { [P in keyof T]: number };
type T51 = T50<any>;  // { [x: string]: number }
type T52 = T50<unknown>;  // {}

在以上代碼中,T50 類型被稱爲映射類型,在映射過程中,如果 key 的類型是 never 類型,則當前 key 將會被過濾掉,所以 T52 的類型是空對象類型。

關於 any 類型和 unknown 類型的區別就介紹到這裏,現在我們來做個總結:

在平時工作中,爲了保證類型安全,我們應該儘可能使用 unknown 類型。最後我們來看一下 unknown 類型與不同類型進行類型運算的結果:

// In an intersection everything absorbs unknown
type T00 = unknown & null;  // null
type T01 = unknown & undefined;  // undefined
type T02 = unknown & null & undefined;  // null & undefined (which becomes never in union)
type T03 = unknown & string;  // string
type T04 = unknown & string[];  // string[]
type T05 = unknown & unknown;  // unknown
type T06 = unknown & any;  // any

// In a union an unknown absorbs everything
type T10 = unknown | null;  // unknown
type T11 = unknown | undefined;  // unknown
type T12 = unknown | null | undefined;  // unknown
type T13 = unknown | string;  // unknown
type T14 = unknown | string[];  // unknown
type T15 = unknown | unknown;  // unknown
type T16 = unknown | any;  // any

any 類型比較特殊,該類型與任意類型進行交叉或聯合運算時,都會返回 any 類型。

閱讀完本文之後,相信你已經瞭解 any 類型和 unknown 類型之間的區別了。**你知道如何檢測 any 類型和 unknown 類型麼?**如果知道的話,可以在評論區提交你的答案。你喜歡以這種形式學 TS 麼?喜歡的話,記得點贊與收藏喲。

掃碼查看 輕鬆學 TypeScript 系列視頻教程

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/-83tlUcG2mOm79nVI2pu3g