跳到主要内容

Utility Types(ts內建的條件型別)

Utility Types 就像函式一樣可以帶入 input 得到 output,透過 Utility Types 將可以「根據一個型別,來建立出另一個型別」。

Keys

建立一個 Utility Type 來取出所有物件型別的 keys 且只要 string

// Utility Type
type Keys<T> = keyof T & string;

使用的方式:

type Manufacture = 'Apple' | 'Google' | 'Samsung' | 'Sony';
type Product = {
name: string;
price: number;
manufacturer: Manufacture;
isLaunched: boolean;
};
type KeysOfProduct = Keys<Product>; // "name" | "price" | "manufacturer" | "isLaunched"

這時候 KeysOfProduct 的結果會是 "name" | "price" | "manufacturer" | "isLaunched"

Values

Values 這個 Utility Type 則是可以取出物件型別的所有屬性值(型別) 使用的方式一樣可以把物件型別 Product 當成參數帶入:

// Utility Type
type Values<T> = T[keyof T];
// use
type ValuesOfProduct = Values<Product>; // string | number | boolean

最後會得到新的型別 ValuesOfProduct

PickObj

它的作用是取出物件型別中的某個 key 的屬性值(型別)

// Utility Type
type PickObj<T, U extends keyof T> = T[U];

這裡一樣要記得 U extends keyof T 的使用,如果沒有限制泛型 U 一定要是物件型別 T 的 key,TypeScript 因為沒辦法確保能在 T 中找到 U 這個 key,將會報錯

// use
type Price = PickObj<Product, 'price'>; // number

如此就可以取出物件型別中 key 為 price 的屬性值的型別。

條件型別 Conditional Types

這並不完全是 TypeScript 的新功能,只能說算是部分新、但又是從泛用型別延伸出來的 —— 它是衍伸物,並不太需要刻意立下一個新的型別種類。 舉例來說: Partial 就是 TypeScript 內建的,不過這些都被官方稱為 Utility Types,冠上的是這個 Utility 這個名稱,實質上是用條件型別的方式去實踐,但本質還是泛用型別的一種延伸出的表現行為。

type NewType = X extends Y ? true : false;

上面的意思翻成白話文就是「如果 X 是 Y 的子集合的話,則 NewType 就會是 true,否則的話 NewType 會是 false」。

X extends Y 表示 Y 是比 X 更大的集合:

extends

範例1
type SomeType1 = 'any-string'; // String Literal Types
type NewType1 = SomeType1 extends string ? true : false; // true

這時因為 SomeType1 是 String Literal Types,它是 string 的子集合,所以 NewType1 就會是 true

範例2
type SomeType2 = 0; // Number Literal Types
type NewType2 = SomeType2 extends string ? true : false; // false

這時候因為 SomeType2 是 Number Literal Types,並不是 string 的子集合,因此 NewType2 就會是 : 後的 false

Literal Types 字面值型別

Literal Types 字面值型別

在 TypeScript 中,可以直接使用「布林值」、「字串值」和「數值的值」作為型別(賦予確切的值),這種用法稱作 Literal Types 字面值型別

使用 Indexed Access Types 取得陣列型別中元素的型別

當時提到的是針對物件型別來使用 Indexed Access Type 的話,可以取出物件型別中屬性值的型別,但如是針對陣列型別的話,這可以使用 Indexed Access Types 來取出陣列型別中元素型別,因為陣列型別的 index 一定是 number 型別的緣故,所以只要使用 SomeArray[number] 就可以取出 SomeArray 這個陣列型別裡面元素值的型別。

舉例來說:

type StringArray = string[];
type StringArrayElement = StringArray[number]; // string

type NumberArray = number[];
type NumberArrayElement = NumberArray[number]; // number

當然也可以用實際數值的方式取出某 index 元素的型別:

type SomeArray = [string, boolean, number];
type Element0 = SomeArray[0]; // string
type Element1 = SomeArray[1]; // boolwwean
type Element2 = SomeArray[2]; // number
type Elements = SomeArray[number]; // string | boolean | number

Partial<Type>

Required<Type>

/* 條件型別的應用 Conditional Types */
// 應用部分官方是稱作 Utility Type - 以下以 Required 為例子
interface PersonalInfo {
name: string;
age?: number;
hasPet?: boolean;
}

// 至少有 name 但不一定需要 age 或 hasPet
let validPersonalInfo: PersonalInfo = {
name: 'Maxwell',
hasPet: false,
};

// 多了不相關的一鍵就會被 TypeScript 警告
let wrongPersonalInfo: PersonalInfo = {
name: 'Maxwell',
age: 20,
hasMotorcycle: true,
};

// 但被冠上 Require 條件型別後,TypeScript 就會
// 嚴格要求 age 與 hasPet 必須存在
let incompletePersonalInfo: Required<PersonalInfo> = {
name: 'Maxwell',
age: 20,
};

Readonly<Type>

Record<Keys, Type>

Pick<Keys, Type>

Omit<Keys, Type>