[TypeScript] 用生活例子圖解 Utility Types
TypeScript 系列文
1. The Very Basics for TS
2. Generics 的使用情境
3. The `extends` keyword
4. Generics — Mapped Types
5. 用生活例子圖解 Utility Types
6. 優雅的在 React 中使用 TS
7. 用 ts-migrate 仙女棒讓 JS 專案瞬間 migrate 成 TS
TS 本身有許多內建的 Utility Types 方便常見的型別轉換,幫助我們在建立或定義客製 type 時更快速,先放一個我覺得很不錯的三分鐘入門影片讓你快速進入 Utility Type 的世界
接下來文章是我消化過後的圖解!開始之前必須要知道不同 type 會有不同對應的 Utility Types,所以我大致分成五大類 type,每一類都有屬於他們自己的 Utility Types,五大類分別為 Object Type 、Union Type、 Function Type、Promise 、 String Manipulation Types
🔖 文章索引
1. Object Type
2. Union Type
3. Function Type
4. Promise
5. String Manipulation Types
Note. 為了好理解我會把這些 Utility types 原始定義方法給列出來,實際上使用是不需要寫的
Object type
把屬性設為 optional的
Partial<T>
、 屬性全部為 required 的Required<T>
、把特定屬性踢掉的Omit<T, K>
、只想要特定屬性的Pick<T, K>
,以及Readonly<T>
、Record<K,T>
,原理其實都是為上一篇 mapped type 的應用
// 我就是一個 Object type
type User = {
id: string;
name: string;
age: number;
}
以生活中例子來說 Object Type 就是一個抽抽樂中的桶子,裡面有很多等著被抽中的紙卡 ( properties ,或稱作 Keys),Utility types 則提供不同的抽獎方式
Partial<T>
Creates a type that makes all properties in T optional
我可以選擇要不要抽獎 : Object type 裡面的紙卡 (property) 是可有可無的
/*
Partial<T>: 把 T 裡的所有屬性都設為 optional
* T 就是傳進來的 Object Type
* P: 就是 property 屬性
*/
type Partial<T> = {
[P in keyof T]?: T[P];
};
Partial<T>
回傳一個新的物件型別,會將傳進來的<T>
這個物件型別中所有屬性都設定為 optional 。
type User = {
id: string;
name: string;
age: number;
}
type PartialUser = Partial<User>
/* Inferred Type
{
id?: string|undefined;
name?: string|undefined;
age?: number|undefined;
};
*/
// 以下三種都給過,屬性不必全部都寫
const A: PartialUser = {
id: 'id',
name: 'Hannah',
age: 18
}
const b: PartialUser = {
id: 'id',
}
const c: PartialUser = {}
Required<T>
Creates a type that all of the properties are required
我要把所有紙卡都抽出來!
/*
Required<T>: 把 T 裡的所有屬性都設為必填
* T 就是傳進來的 Object Type
* P: 就是 property 屬性
* - 這個符號就是把所有擁有 ? 的屬性移除
*/
type Required<T> = {
[P in keyof T]-?: T[P];
};
Required<T>
回傳一個新的物件型別,會將傳進來的<T>
這個物件型別中所有屬性都設定為必填,和 Partial<Type>
正好相反。
type User = {
id: string|undefined;
name: string|undefined;
age: number;
}
type RequiredUser = Required<User>
/* Inferred Type
{
id: string;
name: string;
age: number;
};
*/
const A: RequiredUser = {
id: 'id',
name: 'Hannah',
age: 18
}
// error: 少了 name 跟 age 屬性
const b: RequiredUser = {
id: 'id',
}
Pick<T, K>
Creates a type that only get the properties that we pass to pick
Object type 這個抽抽樂盒子為透明,我要抽出特定的紙卡 (keys)
/*
Pick<T,K>: 只取得在 T 裡面的 K
* T 是傳進來的 Object Type
* K(為 string or Union type) 是想取得的 keys
*/
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
Pick<T,K>
用途在把傳進來的<T>
這個物件型別中選取一組屬性 K(string or union)來建立一個新型別。
type PickUser = Pick<User, "id" | "name">
/* Inferred Type
{
id: string;
name: string;
};
*/
const A: PickUser = {
id: 'id',
name: 'Hannah',
}
// error: 'age' does not exist in type 'PickUser'
const b: PickUser = {
id: 'id',
age: 18
}
Omit<T, K>
Creates a type that remove properties we pass to omit
在抽獎前把不需要的紙卡 (keys) 丟掉,然後剩下的我整盒都要
/*
Omit<T, K>: 把 T 這個 object type 裡的 K 都踢掉
* T 是傳進來的 Object Type
* K 就是 keys (為 Union type),可以是任何屬性名稱
* Exclude<keyof T, K> : 把 K 這個屬性從 T 的所有屬性中除掉
* Pick<T, keys>: 回傳 keys 這個 Object Type
*/
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
Omit<T,K>
用途在把傳進來的<T>
這個物件型別中排除一組屬性 K(string or union)來建立一個新型別,和 Pickt<T,K>
正好相反。以 User 這個 Object Type 來說,Omit<User, “id” | “name”>
等於Pick<User, “age”>
type User = {
id: string;
name: string;
age: number;
}
type OmitUser = Omit<User, "id" | "name">
/* Inferred Type
{ age: number };
*/
const A: OmitUser = {
age: 18,
}
// error: 'id' does not exist in type 'OmitUser'.
const b: OmitUser = {
id: 'id',
age: 18
}
Record<K, T>
Construct a type with a set of properties K of type T
這個跟抽抽樂無關,是先寫你要的紙卡們 (keys),然後把它們通通指定為特定型別 (可以是 object type 或 boolean, number 等任何型別)
/*
Record<K, T>: 使 K 的型別為 T
* T 是想要的型別,任何型別都可
* K 就是 keys (為 Union type),可以是任何屬性名稱
*/
type Record<K extends keyof any, T> = {
[P in K]: T;
};
Record<Keys, Type>
用來將一種型別的屬性 mapped 到另一種型別。很適合拿來做各種排列組合使用,例如抓已有的 type key 然後指派成其他型別
// Inferred Type: {darkMode: boolean, mobile: boolean}
Record<"darkMode" | "mobile", boolean>
// Inferred Type: {[x: string]: boolean}
Record<any, boolean>
/**
Inferred Type:
{ hi : {
name: string;
color: string;
count: number;
}}
*/
Record<'hi', Fruit>
Readonly<T>
Creates a type with all properties of another type set to readonly.
所有屬性只能讀取不能寫入
/*
Readonly<T>: 把 T 裡的所有屬性都設為只能讀取不能寫入
* T 是傳進來的 Object Type
*/
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
Readonly<Type>
這個工具型別也會回傳一個新的型別,主要是將Type
的所有屬性都變成唯讀。
type ReadonlyUser = Readonly<User>
/* Inferred Type
{
readonly id: string;
readonly name: string;
readonly age: number;
};
*/
Multiple utility types
實務上也可以一次可以用好幾個 utility types 來達到你想要的
type User = {
id: string;
name: string;
age: number;
}
/* Inferred Type
{
id?: any;
name?: any;
};
*/
type TUtils = Partial<Pick<Record<string, any>, 'id' | 'name'>>
Union Types
移除掉特定值的
Exclude<T,U>
、只抓取特定值的Extract<T, U>
、把所有 null 跟 undefined 移除的NonNullable<T>
// 我是一個 Union type
type Role = "admin" | "user" | "anonymous"
以生活中例子來說 Union type 就是你手中的牌,Utility types 則是用不同方式讓你決定留在你手中的牌該有哪幾張
Exclude<T, U>
Creates a type help to remove members of a union
把手中不想要的牌丟掉
/*
Exclude<T, U>: 把 T 裡的 U 移除
* T 是一個 union type
* U 就要移除的 Union,也就是 Exclude members,可以是 primitive 值或 Object
* `never` 代表 the type of values that never occur,也就是不要取值
*/
type Exclude<T, U> = T extends U ? never : T;
Exclude<T, U>
用途在把傳進來的<T>
這個 Union Type 中排除一組屬性 U
(string or union)來建立一個新新的 Union Type。
type Role = "admin" | "user" | "anonymous"
type ExcludeUser = Exclude<Role, "admin" | "user">
// Inferred Type: "anonymous"
const Result: ExcludeUser = "anonymous"
type A = Exclude<1 | 2 | 3, 2>;
// Inferred Type: 1 | 3
type B = Exclude<1 | "a" | 2 | "b", number>;
// Inferred Type: "a" | "b"
type C = Exclude<1 | "a" | 2 | "b", 1 | "b" | "c">;
// Inferred Type: "a" | 2
甚至還有一些比較進階用法可以移除含有特定結尾名稱的 type,例如 ${string}${“id” | “Id”}${string}
// 範例ㄧ
/* Remove strings with a certain prefix/suffix from a union */
type ObjectKey =
| "userId"
| "postId"
| "id"
| "userName"
| "postName";
type NonNameKey = Exclude<ObjectKey, `${string}Name`>;
// Inferred Type: "userId" | "postId" | "id"
// 範例二
/* Remove strings with one of several possible values from a union */
type ObjectKey =
| "userId"
| "postId"
| "id"
| "userName"
| "postName";
type NonIdKey = Exclude<
ObjectKey,
`${string}${"id" | "Id"}${string}`
>;
// Inferred Type: "userName" | "postName"
被丟棄的 type 不止原始值,甚至連 Object 也可以
// 範例三
/* Exclude members of a discriminated union by shape */
type Routes =
| {
route: "/user";
search: {
id: string;
};
}
| {
route: "/user/create";
}
| {
route: "/user/edit";
search: {
id: string;
};
};
type RoutesWithoutSearch = Exclude<
Routes,
{
search: any;
}
>;
// Inferred Type: { route: "/user/create" }
Extract<T, U>
Creates a type help to grab out a specific member of the union
把手中想要的牌留起來,其他丟掉
/*
Exclude<T, U>: 只抓取 T 裡的 U
* T 是一個 union type
* U 要抓取的 Union
*/
type Extract<T, U> = T extends U ? T : never;
Extract<T, U>
用途在把傳進來的<T>
這個 Union Type 中選取一組屬性 U
(string or union)來建立一個新的 Union Type ,和 Exclude<T,U>
正好相反。
type Role = "admin" | "user" | "anonymous"
type ExtractUser = Extract<Role, "admin" | "user">
/* Inferred Type:
type ExtractUser = "admin" | "user"
*/
type Extract<A, B> = A extends B ? A : never;
// Inferred Type: 1 | 2
type Result1 = Extract<1 | "a" | 2 | "b", number>;
// Inferred Type: 1 | "b"
type Result2 = Extract<1 | "a" | 2 | "b", 1 | "b" | "c">;
NonNullable<T>
Creates a type that excluding
null
andundefined
from T
type 不能為null
或undefined
/*
NonNullable<T>: 把 T 裏面的null 或 undefined 移除
* T 是一個 union type
*/
type NonNullable<T> = T & {};
NonNullable<T>
這個工具型別會把傳進來的 Union Type<T>
中的 null
and undefined
都排除,然後回傳一個新的 Union Type。
type MaybeStrting = string | null | undefined
type DefinitelyString = NonNullable<MaybeStrting>
/* Inferred Type
type DefinitelyString = string
*/
Function Types
抓取回傳值型別的
ReturnType<T>
、抓取參數型別的Parameters<T>
// 我是一個 Function type
type Func = (a: number, b: number) => number
ReturnType<T>
If we want to access the type return from function
建立一個新的型別為回傳值的 type
/*
ReturnType<T>: 為回傳值的 type
* T 是一個 function type
* `infer R`表示待推斷的回傳值型別
*/
type ReturnType<T extends (...args: any) => any
> = T extends (...args: any) => infer R ? R : any;
type Func = (a: number, b: number) => number
type ReturnValue = ReturnType<Func>
/* Inferred Type
type ReturnValue = number
*/
type a = ReturnType<() => void> // void
type b = ReturnType<() => string | number> // string | number
type c = ReturnType<() => any> // any
Parameters<T>
Get tuple of the function parameters
建立一個新的型別為函示參數的 type
/*
Parameters<T>: 為函示參數的型別
* T 是一個 function type
* `infer P`表示待推斷的參數型別
*/
export type Parameters<T extends (...args: any) => any
> = T extends (...args: infer P) => any
? P
: never;
type Func = (a: number, b: number) => number
type Params = Parameters<Func>
/* Inferred Type
type Params = [a: number, b: number]
*/
Promise Type
抓取回傳值型別的
ReturnType<T>
、抓取參數型別的Parameters<T>
// 我是一個 Promise type
type PromiseStr = Promise<string>
type Result = Awaited<PromiseStr>
Awaited<Type>
This type is meant to model operations like
await
inasync
functions, or the.then()
method onPromise
- specifically, the way that they recursively unwrapPromises
const func = async () => ({
id: 123
})
// Inferred Type: Promise<{id: number}>
type Result1 = ReturnType<typeof func>
// Inferred Type: {id: number}
type Result2 = Awaited<ReturnType<typeof func>>
String Manipulation Types
簡立新的型別轉換你傳進來的字串為大寫
Uppercase<StringType>
、小寫Lowercase<StringType>
、首字大寫Capitalize<StringType>
、取消首字大血Uncapitalize<StringType>
等等。
type UppercaseWes = Uppercase<"wes">;
// type UppercaseWes = "WES"
type LowercaseWes = Lowercase<"Wes">;
// type LowercaseWes = "wes"
type CapitalizeWes = Capitalize<"wes">;
// type CapitalizeWes = "Wes"
type UncapitalizeWes = Uncapitalize<"Wes">;
// type UncapitalizeWes = "wes"
以下例子就是當你想轉換 key 值為首字大寫時,用 String Manipulation Types 方法操作就非常容易。
export type CharacterType = {
name: string;
alignment: string;
intelligence: number;
strength: number;
speed: number;
durability: number;
power: number;
combat: number;
total: number;
};
type TableRowProps = {
heading: Capitalize<keyof CharacterType>;
value: CharacterType[keyof CharacterType];
};
/* Inferred Type
type TableRowProps = {
heading:
| "Name"
| "Alignment"
| "Intelligence"
| "Strength"
| "Speed"
| "Durability"
| "Power"
| "Combat"
| "Total";
value: string | number;
};
*/
整理圖表
把 utility type 整理成圖表之後查找比較容易