TypeScript高級類型-實用技巧

TypeScript高級類型-實用技巧

預備知識

  1. TypeScript高級類型-Partial
  2. TypeScript高級類型-條件類型

類型遞歸

在 TypeScript 中有這樣一個內置類型工具 Required<T>,它可以將對象類型 T 上的所有 可選屬性 轉化爲 必填屬性

先看一下 Required<T> 是如何定義的

/**
 * Make all properties in T required
 */
type Required<T> = {
    [P in keyof T]-?: T[P];
};

類型轉化過程如下:

interface IUser {
  name: string;
  age: number;
  sex?: string;
  department?: string;
  address?: string;
}
  
type requiredUser = Required<IUser>
/* type requiredUser = {
  name: string;
  age: number;
  sex: string;
  department: string;
  address: string;
} */

經過 Required<T> 處理後,可以看到類型 requiredUser 裏面的所有屬性都被轉爲了必選屬性。

現在假如我們有如下一個具有嵌套的類型,再看一下轉化結果

interface ICompany {
  id: string;
  companyName: string;
  companyAddress?: string;
}

interface IUser {
  name: string;
  age: number;
  sex?: string;
  department?: string;
  address?: string;
  company: ICompany; // 嵌套類型中包含可選屬性
}


type requiredUser = Required<IUser>
/* type requiredUser = {
    name: string;
    age: number;
    sex: string;
    department: string;
    address: string;
    company: ICompany; // 嵌套類型中的可選類型未被轉爲必選屬性
} */

轉化結果發現嵌套類型 ICompany 中的可選屬性並未被轉化爲必選屬性,這是因爲 Required<T> 只被用於當前類型 T 的轉化,而對於內部嵌套的類型並不做處理。

這裏想處理深層嵌套屬性,就必須用到類型遞歸

結合 JavaScript 的處理方式,我們可以得到 TypeScript 的處理方式

type DeepRequired<T> = {
  [P in keyof T]-?: T[P] extends object ? DeepRequired<T[P]> : T[P]
};

判斷屬性值類型是否爲對象類型,是則遞歸類型轉化操作。

看一下轉化結果

type requiredUser = DeepRequired<IUser>
/* type requiredUser = {
    name: string;
    age: number;
    sex: string;
    department: string;
    address: string;
    company: DeepRequired<ICompany>;
} */

注意:

如果 IUser 中的 company可選屬性,那麼經過 DeepRequired<IUser> 轉化後,依然爲 company: ICompany; // 嵌套類型中的可選類型未被轉爲必選屬性

經過排查不難發現,當 company 爲可選屬性,那麼其對應的類型爲 ICompany | undefined,此時可以得到 ICompany | undefined extends object,並不滿足賦值條件,故而得到 false 分支,即(ICompany)。

特殊關鍵字

其中 +- 這兩個關鍵字用於映射類型中給屬性添加修飾符,比如我們再上文中 Required<T> 的定義

type Required<T> = {
    [P in keyof T]-?: T[P];
};

type RemoveReadonly<T> = {
  -readonly [P in keyof T]: T[P]
}

用到了 -?,它代表將可選屬性變爲必選,-readonly 代表將只讀屬性變爲非只讀。

註釋

我們可以使用 JSDoc 的方式來添加註釋,藉助於 IDE 可以給我們提供更友好的提示

enum EStatus {
  /**
   * 成功狀態
   */
  Success,
  /**
   * 失敗狀態
   */
  Error
}
// 在調用對應狀態的時候,會給出友好的文字提示

is 關鍵字

is 關鍵字經常用於依賴布爾值的判斷來縮小參數的類型範圍

比如:

function isString(test: any): test is string{
    return typeof test === 'string';
}

function getStringLength(foo: number | string){
    if(isString(foo)){
        console.log(foo.length); // string function
    }
}

getStringLength 在執行時,當 isString 返回 true 時,可以明確知道 foo 是字符串類型,故而調用 length 屬性正常運行。

然而用下面這種方式則會報錯

function isString(test: any): boolean{
    return typeof test === 'string';
}

經過該函數的判斷並不能明確 foo 的類型,故而調用 length 報錯。

泛型約束

在使用泛型的過程中,我們的泛型幾乎可以是任何類型

/**
 * Make all properties in T readonly
 */
type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

如果我們可以明確傳入的泛型是屬於哪一類,那麼應用泛型約束將會使得代碼有更好可維護性。

比如,我們知道上面代碼中的泛型 T 屬於 object 類型,那麼對其添加泛型約束如下:

type ObjectReadonly<T extends object> = {
    readonly [P in keyof T]: T[P];
};

這樣當我們在使用 工具類型 ObjectReadonly<T> 時,將會限制我們只能傳入 object 類型。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章