1. 程式人生 > >TypeScript基礎入門之高階型別的對映型別

TypeScript基礎入門之高階型別的對映型別

高階型別

對映型別

一個常見的任務是將一個已知的型別每個屬性都變為可選的:

interface PersonPartial {
    name?: string;
    age?: number;
}

或者我們想要一個只讀版本:

interface PersonReadonly {
    readonly name: string;
    readonly age: number;
}

這在JavaScript裡經常出現,TypeScript提供了從舊型別中建立新型別的一種方式 — 對映型別。 在對映型別裡,新型別以相同的形式去轉換舊型別裡每個屬性。 例如,你可以令每個屬性成為 readonly型別或可選的。 下面是一些例子:

type Readonly<T> = {
    readonly [P in keyof T]: T[P];
}
type Partial<T> = {
    [P in keyof T]?: T[P];
}

像下面這樣使用:

type PersonPartial = Partial<Person>;
type ReadonlyPerson = Readonly<Person>;

下面來看看最簡單的對映型別和它的組成部分:

type Keys = 'option1' | 'option2';
type Flags = { [K in Keys]: boolean };

它的語法與索引簽名的語法型別,內部使用了 for .. in。 具有三個部分:

1. 型別變數 K,它會依次繫結到每個屬性。 2. 字串字面量聯合的 Keys,它包含了要迭代的屬性名的集合。 3. 屬性的結果型別。 在這個簡單的例子裡, Keys是硬編碼的的屬性名列表並且屬性型別永遠是boolean,因此這個對映型別等同於:

type Flags = {
    option1: boolean;
    option2: boolean;
}

在真正的應用裡,可能不同於上面的 Readonly或 Partial。 它們會基於一些已存在的型別,且按照一定的方式轉換欄位。 這就是 keyof和索引訪問型別要做的事情:

type NullablePerson = { [P in keyof Person]: Person[P] | null }
type PartialPerson = { [P in keyof Person]?: Person[P] }

但它更有用的地方是可以有一些通用版本。

type Nullable<T> = { [P in keyof T]: T[P] | null }
type Partial<T> = { [P in keyof T]?: T[P] }

在這些例子裡,屬性列表是 keyof T且結果型別是 T[P]的變體。 這是使用通用對映型別的一個好模版。 因為這類轉換是 同態的,對映只作用於 T的屬性而沒有其它的。 編譯器知道在新增任何新屬性之前可以拷貝所有存在的屬性修飾符。 例如,假設 Person.name是隻讀的,那麼 Partial<Person>.name也將是隻讀的且為可選的。

下面是另一個例子, T[P]被包裝在 Proxy<T>類裡:

type Proxy<T> = {
    get(): T;
    set(value: T): void;
}
type Proxify<T> = {
    [P in keyof T]: Proxy<T[P]>;
}
function proxify<T>(o: T): Proxify<T> {
   // ... wrap proxies ...
}
let proxyProps = proxify(props);

注意 Readonly<T>和 Partial<T>用處不小,因此它們與 Pick和 Record一同被包含進了TypeScript的標準庫裡:

type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
}
type Record<K extends string, T> = {
    [P in K]: T;
}

Readonly, Partial和 Pick是同態的,但 Record不是。 因為 Record並不需要輸入型別來拷貝屬性,所以它不屬於同態:

type ThreeStringProps = Record<'prop1' | 'prop2' | 'prop3', string>

非同態型別本質上會建立新的屬性,因此它們不會從它處拷貝屬性修飾符。

由對映型別進行推斷

現在你瞭解瞭如何包裝一個型別的屬性,那麼接下來就是如何拆包。 其實這也非常容易:

function unproxify<T>(t: Proxify<T>): T {
    let result = {} as T;
    for (const k in t) {
        result[k] = t[k].get();
    }
    return result;
}

let originalProps = unproxify(proxyProps);

注意這個拆包推斷只適用於同態的對映型別。 如果對映型別不是同態的,那麼需要給拆包函式一個明確的型別引數。