【TS】506- TypeScript 交叉類型

一、簡介

TypeScript 交叉類型是將多個類型合併為一個類型。這讓我們可以把現有的多種類型疊加到一起成為一種類型,它包含了所需的所有類型的特性。

interface IPerson {    id: string;    age: number;  }    interface IWorker {    companyId: string;  }    type IStaff = IPerson & IWorker;    const staff: IStaff = {    id: 'E1006',  age: 33,    companyId: 'EXE'  };    console.dir(staff)

在上面示例中,我們首先為 IPerson 和 IWorker 類型定義了不同的成員,然後通過 & 運算符定義了 IStaff 交叉類型,所以該類型同時擁有 IPerson 和 IWorker 這兩種類型的成員。那麼現在問題來了,假設在合併多個類型的過程中,剛好出現某些類型存在相同的成員,但對應的類型又不一致,比如:

interface X {    c: string;    d: string;  }    interface Y {    c: number;    e: string  }    type XY = X & Y;  type YX = Y & X;    let p: XY;  let q: YX;

在上面的代碼中,接口 X 和接口 Y 都含有一個相同的成員 c,但它們的類型不一致。對於這種情況,此時 XY 類型或 YX 類型中成員 c 的類型是不是可以是 stringnumber 類型呢?比如下面的例子:

p = { c: 6, d: "d", e: "e" };
q = { c: "c", d: "d", e: "e" };

為什麼接口 X 和接口 Y 混入後,成員 c 的類型會變成 never 呢?這是因為混入後成員 c 的類型為 string&number,即成員 c 的類型既是 string 類型又是number 類型。很明顯這種類型是不存在的,所以混入後成員 c 的類型為 never

在上面示例中,剛好接口 X 和接口 Y 中內部成員 c 的類型都是基本數據類型,那麼如果是非基本數據類型的話,又會是什麼情形。我們來看個具體的例子:

interface D { d: boolean; }  interface E { e: string; }  interface F { f: number; }    interface A { x: D; }  interface B { x: E; }  interface C { x: F; }    type ABC = A & B & C;    let abc: ABC = {      x: {        d: true,        e: 'semlinker',        f: 666      }  };    console.log('abc:', abc);

以上代碼成功運行後,控制台會輸出以下結果:

由上圖可知,在混入多個類型時,若存在相同的成員,且成員類型為非基本數據類型,那麼是可以成功合併。前面我們已經介紹了 TypeScript 交叉類型相關的知識,最後我們再來舉一個實際的使用示例。

二、使用示例

在實際項目開發過程中,我們經常需要開發一些功能函數,為了保證函數的靈活性和可復用性,這些函數往往會定義一些輸入參數,而這些參數根據是否必填,又可分為必填參數和可選參數。當必填參數和可選參數有大部分參數是相同的情況下,我們就可以利用 TypeScript 交叉類型來解決復用問題。好了,廢話不多說,直接看個示例:

ArgBase 接口

export interface ArgBase<T> {    name?: string;    description?: string;    hidden?: boolean;    parse: ParseFn<T>;    default?: T | (() => T);    input?: string;    options?: string[];  }

RequiredArg 接口

export type RequiredArg<T> = ArgBase<T> & {    required: true;    value: T;  }

OptionalArg 接口

export type OptionalArg<T> = ArgBase<T> & {    required: false;    value?: T;  }

顧名思義,ArgBase 接口是基礎參數接口,它是 RequiredArg 和 OptionalArg 接口的公共部分。示例代碼中,RequiredArg 和 OptionalArg 的差異就是 required 字段和與之對應的 value 值。通過交叉類型,可以讓我們更好地進行代碼復用,並方便地實現把多種類型疊加到一起成為一種新的類型。

三、參考資源

  • tslang-advanced-types
  • typescript-intersection-types