
我們知道在 TypeScript 中一切皆是類型,而 TypeScript 除了能夠創(chuàng)建新類型之外,它還提供了一些工具類型實(shí)現(xiàn)轉(zhuǎn)換現(xiàn)有類型的能力,這些工具類型是 TypeScript 內(nèi)置的,同時(shí)它們?nèi)挚捎谩?/p>
Partial<Type>
Partial 會(huì)創(chuàng)建一個(gè)新的類型同時(shí)它內(nèi)部所有屬性都變成可選的。
type Type = { x: string, y: string };
// { x?: string; y?: string }
type PartialType = Partial<Type>;
Partial 最常使用的地方是讓一個(gè)對(duì)象的所有屬性都變的可選,比如下面這個(gè)例子,我們?nèi)ジ掠脩舻哪承┬畔ⅲ?/p>
interface User {
name: string;
surname: string;
age: number;
}
const updateUser = (user: User, fields: Partial<User>): User => ({
...user,
...fields,
});
const user1: User = {
name: "John",
surname: "Doe",
age: 17,
};
const user2 = updateUser(user1, { age: 18 });
// { name: "John", surname: "Doe", age: 18 }
console.log(user2);
我們看到 Partial 工具類型非常的好用,簡單方便,我們很有必要研究下它的源碼,這樣當(dāng)官方提供的工具類型不滿足我們的需求的時(shí)候,我們還可以定制化,雖然很少遇到。
Partial 工具類型源碼:
type Partial<T> = {
[P in keyof T]?: T[P];
};
我們看到,Partial 本質(zhì)是一個(gè)——泛型類型別名,里面還用到了索引類型和映射類型。
如果你很熟悉 TS 語法,完整的 Partial 寫法應(yīng)該是這樣的:
type Partial<T> = {
[P in keyof T]+?: T[P]; // 多個(gè)加號(hào)
};
Required<Type>
Required 是 Partial 的反面,Required 創(chuàng)造一個(gè)新類型,同時(shí)內(nèi)部所有的屬性都是必須的。
type Type = { x?: string, y?: string };
// { x: string; y: string }
type RequiredType = Required<Type>;
使用事例例如:
interface User {
name?: string;
surname?: string;
age?: number;
}
class UserManager {
private user: Required<User>;
constructor(user: User) {
this.user = {
name: user.name || "Not Set",
surname: user.surname || "Not Set",
age: user.age || 0,
};
}
getUser() {
return this.user;
}
}
Partial 工具類型的完整源碼我們已經(jīng)知道了,現(xiàn)在 Required 的源碼相信你自己也能實(shí)現(xiàn),很簡單,我們只需要改下類型別名同時(shí)把加號(hào)換成減號(hào)就行了:
type Required<T> = {
[P in keyof T]-?: T[P];
};
Readonly<Type>
Readonly<T> 創(chuàng)建一個(gè)新類型,同時(shí)所有屬性都變?yōu)橹蛔x屬性,這也就意味著這些屬性不能被重新賦值。
type Type = { x: string, y: string };
// { readonly x: string; readonly y: string }
type ReadonlyType = Readonly<Type>;
當(dāng)我們使用 Object.freeze 的時(shí)候,Readonly 是非常的好用的:
interface User {
name: string;
surname: string;
age: number;
}
const user: User = {
name: "John",
surname: "Doe",
age: 18,
};
function freeze<T>(obj: T): Readonly<T> {
return Object.freeze(obj);
}
const readonlyUser = freeze(user);
// Cannot assign to "name" because it is a read-only property
readonlyUser.name = "Andrew";
這時(shí),我們會(huì)很自然的寫出 Readonly 的源碼,如下:
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
上面這個(gè)代碼并不完整,我簡單提醒下你,你就能想出來它的完整代碼了,想一下如何去除有 readonly 屬性的工具函數(shù)。完整版 Readonly 是不是已經(jīng)出現(xiàn)在你的腦海里了,沒錯(cuò)只是簡單的在 readonly 前面加個(gè)加號(hào):
type Readonly<T> = {
+readonly [P in keyof T]: T[P];
};
TypeScript 沒有給出去除 Readonly 修飾符工具函數(shù)此時(shí)我們就可以自己來實(shí)現(xiàn)了,我們把這個(gè)工具類型叫 NonReadonly。
type position = { readonly x: string; readonly y: string};
type NonReadonly<T> = {
-readonly [P in keyof T]: T[P]
}
// type NonReadonlyPos = { x: string; y: string; }
type NonReadonlyPos = NonReadonly<position>;
Record<Keys, Type>
Record<Keys, Type> 創(chuàng)造一個(gè)新類型,同時(shí)將 Keys 中所有的屬性的值的類型轉(zhuǎn)化為 T 類型。
// { x: string; y: string }
type Type = Record<"x" | "y", string>;
Record 可以常用來組合:
interface UserInfo {
age: number;
}
type UserName = "john" | "andrew" | "elon" | "jack";
const userList: Record<UserName, UserInfo> = {
john: { age: 18 },
andrew: { age: 20 },
elon: { age: 49 },
jack: { age: 56 },
};
源碼實(shí)現(xiàn):
type Record<K extends keyof any, T> = {
[P in K]: T;
};
泛型約束 K extends 相信你能看懂,keyof any 你可能有點(diǎn)犯迷糊,keyof any 表示對(duì)象 key 的類型,所以 keyof any === string | number | symbol,不信你可以復(fù)制以下代碼,在 TS 環(huán)境測(cè)試下:
// type unionKeyType = string | number | symbol
type unionKeyType = keyof any;
Exclude<Type, ExcludedUnion>
Exclude 通過排除類型中可分配給 ExcludedUnion 的所有聯(lián)合成員來創(chuàng)建新類型:
// "x" | "y"
type ExcludedType = Exclude<"x" | "y" | "z", "z">;
確定從對(duì)象中獲取固定的 key 非常有用:
interface User {
name: string;
surname: string;
personalNumber: number;
}
type AllowedKeys = Exclude<keyof User, "personalNumber">;
const getUserProperty = (user: User, key: AllowedKeys) => user[key];
const user: User = {
name: "John",
surname: "Doe",
personalNumber: 999999999,
};
const nameProp = getUserProperty(user, "name");
const surnameProp = getUserProperty(user, "surname");
// Argument of type "personalNumber" is not assignable to parameter of type "name" | "surname"
const personalNumberProp = getUserProperty(user, "personalNumber");
源碼展現(xiàn),就一個(gè)簡單的條件類型。
type Exclude<T, U> = T extends U ? never : T;
Extract<Type, Union>
Extract 是 Exclude 的反面。
它通過從可分配給聯(lián)合的類型中提取所有聯(lián)合成員來創(chuàng)建新類型。
// "x" | "y"
type ExtractedType = Extract<"x" | "y" | "z", "x" | "y">;
用來提取兩個(gè)類型的公有屬性名會(huì)非常的合適:
interface Human {
id: string;
name: string;
surname: string;
}
interface Cat {
id: string;
name: string;
sound: string;
}
// "id" | "name"
type CommonKeys = Extract<keyof Human, keyof Cat>;
源碼展示:
type Extract<T, U> = T extends U ? T : never;
Pick<Type, Keys>
Pick 的作用是將 Type 類型中的 Keys 類型提取出來,創(chuàng)建為一個(gè)新類型。
type LongType = {
a: string;
b: string;
c: string;
d: string;
};
// { a: string; b: string }
type ShortType = Pick<LongType, "a" | "b">;
Pick 創(chuàng)建的類型是 Type 類型的子類型,所以它的使用常常是從一個(gè)大類型中提取某些小類型。
interface User {
name: string;
surname: string;
street: string;
house: number;
}
type UserAddress = Pick<User, "street" | "house">;
const address: UserAddress = {
street: "Street",
house: 1,
};
源碼實(shí)現(xiàn),注意下泛型約束 K extends keyof T。
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
Omit<Type, Keys>
Omit 從 Type 的所有屬性中,移除 Keys 鍵用剩下的鍵來創(chuàng)建新類型。
type LongType = {
a: string;
b: string;
c: string;
d: string;
};
// { c: string; d: string }
type ShortType = Omit<LongType, "a" | "b">;
這個(gè)用來刪除類型中的某些不要的屬性非常有用:
interface User {
name: string;
surname: string;
personalNumber: number;
}
type CleanUser = Omit<User, "personalNumber">;
const getUserData = (user: User): CleanUser => {
const { personalNumber, ...rest } = user;
return rest;
};
源碼展示, Pick 的實(shí)現(xiàn)用到了 Exclude 來實(shí)現(xiàn)的:
type Omit<T, K extends keyof any> = { [P in Exclude<keyof T, K>]: T[P]; }
如果你用較早期的 TS ,Omit 的實(shí)現(xiàn)可能是這樣的,效果一樣,思路不通而已:
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
keyof any 等價(jià)于 number | string | symbol,我們知道 number | string | symbol 是能作為對(duì)象的 key 的類型,也就是說 Omit<Type, Keys> 移除鍵的時(shí)候,keys 可以不為 Type 里面的 key ,簡單來講寫法更加寬松。
那么問題來了,移除 key 照理說應(yīng)該是移除對(duì)象里面的 key ,即 K extends keyof any 應(yīng)該改為 K extends keyof T,TS 沒有這么做的道理是啥捏?
原因我想破了腦袋也沒想到,去 Github 搜了下,發(fā)現(xiàn)大家普遍的需求是讓 Omit 的寫法更嚴(yán)謹(jǐn),TS 官方答應(yīng)著,并沒實(shí)現(xiàn),點(diǎn)擊了解 "Omit" type using "keyof any" instead of "keyof T"
如果說,我們想要一個(gè)嚴(yán)格的 Omit,我們可以把 Omit 的 K extends keyof any 改為 K extends keyof T 自己實(shí)現(xiàn)一個(gè)較為嚴(yán)格的 Omit,我們叫它 Remove ,源碼:
type Remove<T, K extends keyof T> = { [P in Exclude<keyof T, K>]: T[P]; }
interface IPerson {
age: number;
name: string;
}
type noAge = Remove<IPerson, "age">; // yes type noAge = { name: string; }
type noRandomKey = Remove<IPerson, "灰機(jī)">; // no
NonNullable<Type>
NonNullable 通過從類型中排除 null 和 undefined 來創(chuàng)建新類型。
基本上,它是 Exclude<T,null | undefined> 的縮寫:
type Type = string | null | undefined;
// "string"
type NonNullableType = NonNullable<Type>;
Parameters<Type>
參數(shù)從函數(shù)類型 Type 的參數(shù)中使用的類型構(gòu)造元組類型:
const addNumbers = (x: number, y: number) => {
return x + y;
};
// [x: number, y: number]
type FunctionParameters = Parameters<typeof addNumbers>;
使用 addNumbers 的時(shí)候?yàn)槭裁催€要加上 typeof 呢?因?yàn)?addNumbers 是 JS 代碼實(shí)現(xiàn),我們需要的是函數(shù)簽名,所以加上 typeof ,如果我們直接給一個(gè)函數(shù)簽名,就不需要加上 typeof ,例如:
type addNumbers = (x: number, y: number) => number;
// [x: number, y: number]
type FunctionParameters = Parameters<addNumbers>;
您還可以檢索單個(gè)參數(shù):
const addNumbers = (x: number, y: number) => {
return x + y;
};
// "number"
type FirstParam = Parameters<typeof addNumbers>[0];
// "number"
type SecondParam = Parameters<typeof addNumbers>[1];
// "undefined"
type ThirdParam = Parameters<typeof addNumbers>[2];
如果獲取函數(shù)參數(shù)的類型以確保類型安全很有用,尤其是在外部使用時(shí):
const saveUser = (user: { name: string; surname: string; age: number }) => {
// ...
};
const user: Parameters<typeof saveUser>[0] = {
name: "John",
surname: "Doe",
age: 18,
};
源碼展示,仔細(xì)看這個(gè)條件泛型,尤其是 infer R:
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
ConstructorParameters<Type>
ConstructorParameters 根據(jù)構(gòu)造函數(shù)的類型構(gòu)造元組或數(shù)組類型。
基本上,它類似于參數(shù),但適用于類構(gòu)造函數(shù):
class UserManager {
private name: string;
private surname: string;
constructor(user: { name: string; surname: string }) {
this.name = user.name;
this.surname = user.surname;
}
}
// "[user: { name: string, surname: string} ]"
type UserManagerConstructorParams = ConstructorParameters<typeof UserManager>;
與 Parameters 類型相同,當(dāng)我們外部使用時(shí),它有助于確保構(gòu)造函數(shù)接受我們的參數(shù):
class UserManager {
private name: string;
private surname: string;
constructor(user: { name: string; surname: string }) {
this.name = user.name;
this.surname = user.surname;
}
}
const params: ConstructorParameters<typeof UserManager>[0] = {
name: "John",
surname: "Doe",
};
源碼展示:
type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;
ReturnType<Type>
ReturnType 構(gòu)造函數(shù)Type的返回類型的類型:
const getUser = () => ({
name: "John",
surname: "Doe",
age: 18,
});
// { name: string; surname: string; age: number; }
type FunctionReturnType = ReturnType<typeof getUser>;
與 Parameters 和 ConstructionParameters 一樣,當(dāng)您外部使用并希望獲得導(dǎo)入函數(shù)的返回類型時(shí),它很有用:
const getUser = () => ({
name: "John",
surname: "Doe",
age: 18,
});
type User = ReturnType<typeof getUser>;
const user: User = {
name: "Andrew",
surname: "Hopkins",
age: 20,
};
源碼展示:
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
InstanceType<Type>
InstanceType構(gòu)建一個(gè)類型包括實(shí)例類型的構(gòu)造函數(shù)的類型。
基本上,它類似于 ReturnType,但作用于類構(gòu)造函數(shù):
class UserManager {
name: string;
surname: string;
constructor(user: { name: string; surname: string }) {
this.name = user.name;
this.surname = user.surname;
}
}
// { name: string; surname: string }
type UserMangerInstanceType = InstanceType<typeof UserManager>;
您可能不會(huì)這樣做,因?yàn)槟梢灾苯邮褂?UserManager 類型:
class UserManager {
name: string;
surname: string;
constructor(user: { name: string; surname: string }) {
this.name = user.name;
this.surname = user.surname;
}
}
const user2: UserManager = {
name: "John",
surname: "Doe",
};
源碼展示:
type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;
ThisParameterType<Type>
提取函數(shù) this 的類型,若函數(shù)類型并沒有此參數(shù),則提取為 unknown 類型。
function toHex(this: Number) {
return this.toString(16);
}
function numberToString(n: ThisParameterType<typeof toHex>) {
return toHex.apply(n);
}
因?yàn)?this 指向的問題,項(xiàng)目中并不常用.
源碼展示:
type ThisParameterType<T> = T extends (this: infer U, ...args: any[]) => any ? U : unknown;
最后
以上源碼展示,均可在 TypeScript 項(xiàng)目的 ./node_modules/typescript/lib/lib.es5.d.ts 路徑找到。
因?yàn)楣俜轿臋n實(shí)施更新的緣故,此文章可能過時(shí),請(qǐng)以官方文檔為準(zhǔn):utility-types