React 中的 TS 類型過濾原來是這麼做的!
大家好,我是零一,相信大家在閱讀同事寫的代碼或者優秀的開源庫的代碼時,一定見過各種各樣的風騷的 TS 寫法,不花點時間下去根本看不懂,換作是我們,可能就直接一個 any
完事了,但是真正當項目體積變大後,你會發現這些 TS 騷操作真的很重要,因爲它能很好地幫助你做靜態類型校驗
今天就來介紹一個在其它開源庫中見到的既花裏胡哨,又實用的 TS 類型——TS 類型過濾
自我介紹
TS 類型過濾,英文名(我自己取的)叫 FilterConditionally,這是它完整的樣子👇
type FilterConditionally<Source, Condition> = Pick<
Source,
{
[K in keyof Source]: Source[K] extends Condition ? K : never
}[keyof Source]
>;
別看很複雜,其實非常有用,它可以從一個對象類型中過濾出你想要的,比如:
interface Example {
a: string; // ✅
b: string; // ✅
c: number; // ❌
d: boolean; // ❌
}
type NewType = FilterConditionally<Sample, string>
/*
NewType 最終結果爲:
{
a: string;
b: string
}
*/
相信大家已經這個類型的作用了,並且你們也很想讀懂它,沒關係,接下來由內而外、一步一步地介紹,一定讓你們完全讀懂,讀不懂評論區來噴我(我說着玩的~)
分步介紹
涉及的知識點比較多,怕有些不熟悉 TS 的同學懵逼,先來介紹其中幾個常見的基礎知識點
開胃小菜
不會耽誤大家多少時間的,會的小夥伴可以直接調過
keyof
關鍵詞 keyof
的名字叫 索引類型查詢操作符,它的作用就像它的字面意思一樣直白:xx 的 key 值
interface Example {
a: string;
b: string;
c: number;
d: boolean;
}
type Keys = keyof Example // 等價於 type Keys = 'a' | 'b' | 'c' | 'd'
你可以把 keyof
簡單理解爲 JavaScript 中的 Object.keys
in
關鍵詞 in
可以遍歷枚舉類型,比如:
type Keys = 'a' | 'b' | 'c' | 'd'
type Obj = {
[T in Keys]: string; // 遍歷Keys,把每個key都賦值string類型
}
/* 等價於
type Obj = {
a: string;
b: string;
c: string;
d: string;
}
*/
你可以把 in
簡單理解爲 JavaScript 中 for...in
的 in
的作用
Conditional
第二個知識點是條件判斷,比如:
interface A {}
interface B extends A {} // B繼承於A
// B是否繼承於A?若是,則爲number類型;若不是,則爲string類型
type C = B extends A ? number : string // 等價於 type C = number
// A是否繼承於B?若是,則爲number類型;若不是,則爲string類型
type D = A extends B ? number : string // 等價於 type D = string
你可以把 A extends B ? number : string
簡單理解爲 JavaScript 中的三元運算符
泛型
泛型我就不多做介紹了,不太瞭解的小夥伴可以直接看 TS 文檔——泛型 [1]
正餐開始
剛剛介紹完 "開胃小菜",那就趁熱打鐵看一個簡單的類型
type MarkUnwantedTypesAsNever<Source, Condition> ={
[K in keyof Source]: Source[K] extends Condition ? K : never
}
一句話介紹這個類型的作用就是:遍歷一個對象類型,將不想要的類型標記爲 never
舉個例子🌰
interface Example {
a: string; // ✅
b: string; // ✅
c: number; // ❌
d: boolean; // ❌
}
// 我只想要Example類型中的string類型的key,非string的就標記爲never
type MyType = MarkUnwantedTypesAsNever<Example, string>
/*
等價於:
type MyType = {
a: 'a';
b: 'b';
c: never;
d: never;
}
*/
稍微講一下小細節,[K in keyof Example]
遍歷了 Example
這個對象類型,然後用條件判斷 Example[K] extends string ? K : never
給對應的 key 值賦值,假設遍歷第一個 key 值爲 a
,那麼 Example[K] = Example[a] = string
,此時就是 string extends string ? 'a' : never
,string
肯定是繼承於 string
的,所以纔會有這樣一個結果
此時大家心頭一驚,爲什麼要把類型搞成這樣??我們最後想要的結果不是要拿到一個 { a:string; b:string }
的類型嗎?別急,後面還有別的操作
再來看一個索引訪問接口屬性的小知識點
type Value = {name: "zero2one"}["name"] // 等價於 type Value = "zero2one"
你可以把它簡單理解成 JavaScript 中訪問對象某個 key 對應的 value
而在 TS 中還有另一種情況:
type Value = {
name: "zero2one";
age: 23
}["name" | "age"]
// 等價於 type Value = "zero2one" | 23
而值爲 never
的 key
值是無法被訪問到的:
type Value = {
name: "zero2one";
age: never
}["name" | "age"]
// 等價於 type Value = "zero2one"
所以接下來可以看更復雜的類型了
type MarkUnwantedTypesAsNever<Source, Condition> ={
[K in keyof Source]: Source[K] extends Condition ? K : never
}[keyof Source]
我們巧妙地利用 keyof
關鍵詞去遍歷訪問所有的接口屬性
// 借用一下剛纔例子的結果
type MyType = {
a: 'a';
b: 'b';
c: never;
d: never;
}['a' | 'b' | 'c' | 'd']
/*
等價於:
type MyType = 'a' | 'b'
*/
到此爲止,我們所做的事情就是:把目標對象類型中想要類型的 key 值篩選了出來
別急別急,離成功就差一步之遙
最後登場的就是 Pick
,這個類型是 TS 內置的,簡單瞭解一下它的作用
// Pick類型的實現
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
}
你可以不去詳細地讀懂它的實現,只需要知道 Pick
的作用就是:篩選出類型T
中指定的某些屬性
舉個簡單的例子:
interface A {
a: 1;
b: 2;
c: 3;
d: 4;
}
type C = Pick<A, 'a' | 'c'> // 等價於 type C = { a: 1; c: 3 }
是的,就是這麼簡單,好了可以來看最終的 BOSS 了
那麼最後再從 Source
中篩選出對應屬性即可,回到本文具體的例子當中,圖中紅框中的值上文已得到爲 type MyType = 'a' | 'b'
,那最後 Pick 一下就好了
interface Example {
a: string;
b: string;
c: number;
d: boolean;
}
// 上文得到的結果
type MyType = 'a' | 'b'
type Result = Pick<Example, MyType> // 等價於 type Result = { a: string; b: string }
// ---- 以上等價於 ---- //
interface Example {
a: string; // ✅
b: string; // ✅
c: number; // ❌
d: boolean; // ❌
}
type NewType = FilterConditionally<Sample, string>
/*
NewType 最終結果爲:
{
a: string;
b: string
}
*/
這就是文章開頭的結果獲取的全過程
實戰應用例子
正如本文標題所說的,TS 類型過濾在很多優秀的開源庫中是非常常見的,比如我們熟悉的 React 中就是:
type ElementType<P = any> = {
[K in keyof JSX.IntrinsicElements]: P extends JSX.IntrinsicElements[K] ? K : never
}[keyof JSX.IntrinsicElements] | ComponentType<P>;
最後
開源庫中像 TS 類型過濾這種場景太多太多了,希望今後大家遇到時能輕鬆讀懂。如果在屏幕前閱讀的你是後端,說不定也能在後端的開源框架源碼中看到它的身影呢~
參考資料
[1]
TS 文檔——泛型: https://www.tslang.cn/docs/handbook/generics.html
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/EkcFlwTu6u9gC-8nvU0olQ