TypeScript 內(nèi)置工具詳談
點(diǎn)擊上方 前端瓶子君,關(guān)注公眾號(hào)
回復(fù)算法,加入前端編程面試算法每日一題群

前言
TypeScript 提供了幾種實(shí)用程序類(lèi)型來(lái)助力常見(jiàn)的類(lèi)型轉(zhuǎn)換。這些實(shí)用程序是全局可用的。
也就是說(shuō)全局聲明了一些Type, 調(diào)用Type就可以方便地進(jìn)行一些類(lèi)型轉(zhuǎn)換或者創(chuàng)建新的類(lèi)型。
不會(huì)這些函數(shù)一樣能寫(xiě)TypeScript你不會(huì)真的就不看下文了吧???, 但是掌握后能讓你寫(xiě)TypeScript事半功倍。且掌握這些內(nèi)置Type是十分必要的。
本文章主要對(duì)一些比較少用或者難理解的類(lèi)型做了比較詳細(xì)的說(shuō)明。比如 ThisType<T> 等
1、Partial 將一個(gè)類(lèi)型的屬性全部變?yōu)榭蛇x
定義
type Partial<T> = {
[P in keyof T]?: T[P];
};
復(fù)制代碼
從上面的代碼中可以看出來(lái)該Type使用時(shí)需要傳入一個(gè)泛型T。內(nèi)部遍歷T的所有屬性然后創(chuàng)建一個(gè)新的 Type,新的Type的所有屬性使用 ? 標(biāo)識(shí),使之為可選。
keyof會(huì)遍歷一個(gè)Interface的所有屬性名稱(chēng)(key), 生成一個(gè)聯(lián)合類(lèi)型 "name" | "age" ...,然后可以得到下面代碼
P in "name" | "age" 這就很明白能看出來(lái)了,表明了P為右側(cè)類(lèi)型
使用案例
interface UserInfo {
name:string;
age:number;
}
// 這里會(huì)將 UserInfo 所有的屬性變?yōu)榭蛇x
const foo:Partial<UserInfo> = {
name:"張三"
}
復(fù)制代碼
2、Required 將一個(gè)類(lèi)型的屬性全部變?yōu)楸剡x
定義
type Required<T> = {
[P in keyof T]-?: T[P];
};
復(fù)制代碼
該Type和Partial剛好是相反的。從上面的代碼中可以看出來(lái)該Type實(shí)用時(shí)需要傳入一個(gè)泛型T。內(nèi)部使用-?將T的每個(gè)屬性去除可選標(biāo)識(shí)使之變成為必填。
使用案例
interface UserInfo {
name?:string;
age?:number;
}
// 這里會(huì)將 UserInfo 所有可選的屬性變?yōu)楸剡x
const foo:Required<UserInfo> = {
name:"張三",
age:18
}
復(fù)制代碼
3、Readonly 將一個(gè)類(lèi)型的屬性全部變?yōu)橹蛔x狀態(tài)
定義
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
復(fù)制代碼
從上面的代碼中可以看出來(lái)該Type實(shí)用時(shí)需要傳入一個(gè)泛型T。內(nèi)部使用readonly將T的每個(gè)屬性去除可選標(biāo)識(shí)使之變成為只讀。
使用案例
interface UserInfo {
name?:string;
age?:number;
}
const foo:Readonly<UserInfo> = {
name:"張三",
age:18
}
foo.name = '李四';// error: 無(wú)法分配到 "name" ,因?yàn)樗侵蛔x屬性
復(fù)制代碼
4、Record 構(gòu)造一個(gè)字面量對(duì)象 Type
定義
type Record<K extends keyof any, T> = {
[P in K]: T;
};
復(fù)制代碼
Record 用于方便地構(gòu)造一個(gè)字面量對(duì)象。其作用和 { [propName:string]:any } 有些許類(lèi)似。
Record 只需要傳入兩個(gè) Type 即可創(chuàng)建一個(gè)新的 Type,相比于 { [propName:string]:any } 能方便一些。當(dāng)然除了方便外功能也比它強(qiáng)大,因?yàn)?code style="font-size: 14px;word-wrap: break-word;border-radius: 4px;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #9b6e23;background-color: #fff5e3;padding: 3px;margin: 3px;">Record第一個(gè)參數(shù)可接收一組key,這樣就可以做到定義出一個(gè)完整的 Type 了。
使用案例
// 這是通過(guò) interface 定義出來(lái)的。
interface UserInfo {
name:string;
age:number;
}
// 我們用 Record 來(lái)實(shí)現(xiàn)一遍 UserInfo 。
// 注意:后面一個(gè)形參和 UserInfo 的是不一樣的,因?yàn)?Record 第二個(gè)參數(shù)只能接受一個(gè)類(lèi)型。所以這里要么用 any,要么用這種聯(lián)合類(lèi)型。
type UserInfoT = Record<"name" | "age", string | number>
// 結(jié)果
// type UserInfoT = {
// name:string | number;
// age:string | number;
// }
復(fù)制代碼
5、Pick 從一個(gè) Type 中選取一些屬性來(lái)構(gòu)造一個(gè)新的對(duì)象 Type
定義
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
復(fù)制代碼
Pick 也用于方便地構(gòu)造一個(gè)字面量對(duì)象。其作用和 Record 有些許類(lèi)似。
使用案例
interface UserInfo {
name:string;
age:number;
}
// 這時(shí)候我們只需要 UserInfo 的 name 屬性。
type UserInfoT = Pick<UserInfo, "name">
復(fù)制代碼
6、Omit 從一個(gè)對(duì)象類(lèi)型中刪除一些屬性來(lái)構(gòu)造一個(gè)新的對(duì)象 Type
定義
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
復(fù)制代碼
日常使用中Omit 是一個(gè)使用頻率可能比較高的。和 Pick 剛剛相反,用于排除不需要的屬性。
使用案例
interface UserInfo {
name:string;
age:number;
}
// 這時(shí)候我們不需要 UserInfo 的 name 屬性。
type UserInfoT = Omit<UserInfo, "name">
復(fù)制代碼
7、Exclude 排除一個(gè)聯(lián)合類(lèi)型中的某一些類(lèi)型來(lái)構(gòu)造一個(gè)新 Type
定義
type Exclude<T, U> = T extends U ? never : T;
復(fù)制代碼
上面說(shuō)的 Omit 和 Pick 都是對(duì)一個(gè)字面量對(duì)象 Type 的操作。如果要對(duì)一個(gè)聯(lián)合類(lèi)型操作的話(huà)需要用到 Exclude 和 Extract
使用案例
// 排除掉 "name"
type UserInfoT = Exclude<"name" | "age", "name">;
// 等價(jià)于
type UserInfoA = "age";
復(fù)制代碼
8、Extract 提取出一個(gè)聯(lián)合類(lèi)型中的某一些類(lèi)型來(lái)構(gòu)造一個(gè)新 Type
定義
type Extract<T, U> = T extends U ? T : never;
復(fù)制代碼
和 Exclude 恰好相反。
使用案例
// 從 T1 中 提取出 T2
type T1 = "name" | "age" | "hob";
type T2 = "name" | "age";
type UserInfoT = Extract<T1, T2>;
// 等價(jià)于
type UserInfoA = "name" | "age";
復(fù)制代碼
既然是提出哪為啥不直接用定義好的 T2?
因?yàn)檫@樣可以保證 UserInfoT 的類(lèi)型一定是在 T1 中存在的;
9、NonNullable 從類(lèi)型中排除 null 和 undefined 來(lái)構(gòu)造一個(gè)新的 Type
定義
type NonNullable<T> = T extends null | undefined ? never : T;
復(fù)制代碼
使用案例
// 從 UserInfoK 中 排除掉 null | undefined
type UserInfoK = NonNullable<"name" | "hob" | undefined>;
// 等價(jià)于
type UserInfoKA = "name" | "hob";
復(fù)制代碼
10、Parameters 從 [函數(shù) Type] 的形參構(gòu)造一個(gè)數(shù)組 Type
定義
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
復(fù)制代碼
infer標(biāo)識(shí)一個(gè)待推導(dǎo)類(lèi)型,上面定義的意思是:如果 T 為函數(shù)類(lèi)型,那就返回函數(shù)的形參。
ps: infer和變量似的,先定義一個(gè) infer P 然后 Ts 就會(huì)自動(dòng)推導(dǎo)函數(shù)的形參或者返回值、或者數(shù)組元素等,然后開(kāi)發(fā)者在合適的位置使用定義好的infer P即可。
一個(gè)簡(jiǎn)單的infer案例。
加入有這樣一個(gè)需求:需要將數(shù)組類(lèi)型的 Type 變?yōu)槁?lián)合類(lèi)型。其他類(lèi)型的則不變。這樣我們就可以寫(xiě)一個(gè)這樣的 Type
type ArrayToUnion<T> = T extends Array<infer Item> ? Item : T;
const a:ArrayToUnion<[string, number]> = "111"; // a: string | number
const b:ArrayToUnion<string | number> = "111"; // a: string | number
復(fù)制代碼
從這個(gè)案列的a變量可以看出作用,a變量的類(lèi)型定義為ArrayToUnion<[string, number]>,這里傳入的是個(gè)數(shù)組[string, number]被ArrayToUnion處理為了string | number。
使用案例
// 定義一個(gè)函數(shù)
function getUserInfo(id:string, group:string){}
// 獲取到函數(shù)需要的形參 Type[]
type GetUserInfoArg = Parameters<typeof getUserInfo>;
const arg:GetUserInfoArg = [ "001", "002" ];
getUserInfo(...arg);
復(fù)制代碼
ps: 上面代碼中的typeof是 ts 提供的操作符不是 js 中的那個(gè)typeof,只能用到 ts 的類(lèi)型定義中, 所以使用typeof getUserInfo才能指向函數(shù)Type
11、ConstructorParameters 從定義的[構(gòu)造函數(shù)]的形參構(gòu)造數(shù)組 Type
定義
type ConstructorParameters<T extends abstract new (...args: any) => any> = T extends abstract new (...args: infer P) => any ? P : never;
復(fù)制代碼
實(shí)現(xiàn)原理完全和 Parameters 一樣,只不過(guò)這個(gè)方法接受的事一個(gè)類(lèi)。
使用案例
class User{
constructor(id:string, group:string){}
}
type NewUserArg = ConstructorParameters<typeof User>;
const arg:NewUserArg = [ "001", "002"];
new User(...arg);
復(fù)制代碼
12、ReturnType 用函數(shù) Type 的返回值定義一個(gè)新的 Type
定義
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
復(fù)制代碼
使用 infer 實(shí)現(xiàn)。比 Parameters 更簡(jiǎn)單,可以去看上面的 Parameters 就能明白這段代碼意思。
使用案例
// 定義一個(gè)函數(shù) Type
type GetUserInfo = ()=>string;
const rt:ReturnType<GetUserInfo> = 'xxx';
復(fù)制代碼
13、InstanceType 從一個(gè)構(gòu)造函數(shù)的實(shí)例定義一個(gè)新的 Type
定義
type InstanceType<T extends abstract new (...args: any) => any> = T extends abstract new (...args: any) => infer R ? R : any;
復(fù)制代碼
使用 infer 實(shí)現(xiàn)。和ReturnType實(shí)現(xiàn)原理完全一樣。
使用案例
// 定義一個(gè)函數(shù) Type
type GetUserInfo = ()=>string;
const rt:ReturnType<GetUserInfo> = 'xxx';
復(fù)制代碼
14、ThisParameterType 提取函數(shù) Type 的 this 參數(shù)生成一個(gè)新的 Type
定義
type ThisParameterType<T> = T extends (this: infer U, ...args: any[]) => any ? U : unknown;
復(fù)制代碼
從上面定義看出該 Type 對(duì)函數(shù)的第一個(gè)形參 this 做了infer推導(dǎo)。然后返回了推導(dǎo)出來(lái)的this。不清楚infer的話(huà),往上翻,去仔細(xì)看看Parameters一節(jié)的說(shuō)明。
使用案例
// 定義一個(gè)函數(shù),并且定義函數(shù) this 類(lèi)型。
function getUserInfo(this:{ name:string }){}
const getUserInfoArgThis: ThisParameterType<typeof getUserInfo> = {
name:"王"
};
復(fù)制代碼
15、OmitThisParameter 忽略函數(shù) Type 的 this 參數(shù),生成一個(gè)新的函數(shù) Type
定義
type OmitThisParameter<T> = unknown extends ThisParameterType<T> ? T : T extends (...args: infer A) => infer R ? (...args: A) => R : T;
復(fù)制代碼
這個(gè)Type看著略微復(fù)雜。咋們拆一下看就會(huì)簡(jiǎn)單很多。
首先說(shuō)明一下這個(gè)Type的這些判斷都是干嘛的。
上面定義意思是:如果傳入的T沒(méi)有this參數(shù)就直接返回T,如果有this參數(shù)就繼續(xù)進(jìn)行判斷,
第二層判斷為:如果T不是函數(shù)那也會(huì)直接返回T,最后是重新定義了一個(gè)函數(shù)然后返回。其中使用infer定義了我們所需要的形參和返回值。
這里在座的各位可能會(huì)在(...args: infer A) => infer R ? (...args: A) => R : T這里產(chǎn)生疑惑。
上面的寫(xiě)法會(huì)直接把this參數(shù)過(guò)濾掉,為了證實(shí)這點(diǎn),我們可以實(shí)現(xiàn)一下:
type NoThis<T> = T extends (...args: infer A) => infer R ? A : T
const a:NoThis<typeof getUserInfo>; // a: [id: string]
復(fù)制代碼
上面代碼中我們直接返回了推導(dǎo)的A,得到了形參A的類(lèi)型。這里面是不會(huì)包含this的。
使用案例
// 定義一個(gè)函數(shù)
function getUserInfo(this:{ name:string }, id:string){}
// 去除 getUserInfo 函數(shù) this 參,然后創(chuàng)建出來(lái)了一個(gè)新類(lèi)型
const aaa: OmitThisParameter<typeof getUserInfo> = (id:string)=>{}
復(fù)制代碼
16、ThisType 給對(duì)象標(biāo)記 this 接口
這個(gè)類(lèi)型在 lib.d.ts 中定義的就是一個(gè){}空標(biāo)簽,所以用的時(shí)候往往比較困惑。特別是沒(méi)注意看到官網(wǎng)上寫(xiě)的必須開(kāi)啟--noImplicitThis時(shí)才可以用的時(shí)候。就算你看到了,但是你在他們案例中如果不注意的話(huà)還是搞不懂,因?yàn)楣俜桨咐性O(shè)置了這個(gè)編譯規(guī)則 // @noImplicitThis: false。
noImplicitThis 規(guī)則開(kāi)啟后在函數(shù)中的this在不定義的情況下不能使用,相當(dāng)于嚴(yán)格模式,默認(rèn)情況下noImplicitThis的值為false,除非手動(dòng)開(kāi)啟,否則ThisType毫無(wú)作用。
使用案例
// 定義一個(gè)函數(shù)
function getUserInfo(this:{ name:string }, id:string){}
// 去除 getUserInfo 函數(shù) this 參,然后創(chuàng)建出來(lái)了一個(gè)新函數(shù)類(lèi)型
const aaa: OmitThisParameter<typeof getUserInfo> = (id:string)=>{}
復(fù)制代碼
17、Uppercase 將字符串中的每個(gè)字符轉(zhuǎn)換為大寫(xiě)
這是對(duì)字符串的操作,所有對(duì)字符串的操作在 lib.d.ts 中都找不到具體的定義,文檔上說(shuō)是為了提升性能。
type MyText = "Hello, world"
type A = Uppercase<MyText>; // type A = "HELLO, WORLD"
復(fù)制代碼
18、Lowercase 將字符串中的每個(gè)字符轉(zhuǎn)換為小寫(xiě)
type MyText = "Hello, world"
type A = Lowercase<MyText>; // type A = "hello, world"
復(fù)制代碼
19、Capitalize 將字符串中的第一個(gè)字符轉(zhuǎn)換為大寫(xiě)
type MyText = "hello, world"
type A = Capitalize<MyText>; // type A = "Hello, world"
復(fù)制代碼
20、Uncapitalize 將字符串中的第一個(gè)字符轉(zhuǎn)換為小寫(xiě)
type MyText = "Hello, world"
type A = Uncapitalize<MyText>; // type A = "hello, world"
復(fù)制代碼
以上就是全部的內(nèi)容啦~
一款 javascript AST 節(jié)點(diǎn)操作插件推薦:
qnn-object-ast-handle[1] -使用操作 js 字面量對(duì)象的方式來(lái)操作代碼文件。使 AST 節(jié)點(diǎn)操作變得毫不費(fèi)力。
關(guān)于本文
來(lái)源:愛(ài)玫瑰的小王子
https://juejin.cn/post/6988364988427534349
