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);
注意這個拆包推斷只適用於同態的對映型別。 如果對映型別不是同態的,那麼需要給拆包函式一個明確的型別引數。