TypeScript中的類型運算符實現(xiàn)
1. keyof運算符
1. 簡介
是一個單目運算符,接受一個對象類型作為參數(shù),返回該對象的所有鍵名組成的聯(lián)合類型。
type MyObj = {
foo: number,
bar: string,
};
type Keys = keyof MyObj; // 'foo'|'bar'
這個例子keyof MyObj返回MyObj的所有鍵名組成的聯(lián)合類型,即'foo'|'bar'
由于 JavaScript 對象的鍵名只有三種類型,所以對于任意對象的鍵名的聯(lián)合類型就是string|number|symbol。
對于沒有自定義鍵名的類型使用 keyof 運算符,返回never類型,表示不可能有這樣類型的鍵名
type KeyT = keyof object; // never
上面示例中,由于object類型沒有自身的屬性,也就沒有鍵名,所以keyof object返回never類型。
由于 keyof 返回的類型是string|number|symbol,如果有些場合只需要其中的一種類型,那么可以采用交叉類型的寫法。
type Capital<T extends string> = Capitalize<T>; type MyKeys<Obj extends object> = Capital<keyof Obj>; // 報錯 type MyKeys<Obj extends object> = Capital<string & keyof Obj>;
這個列子中,string & keyof Obj等同于string & string|number|symbol進行交集運算,最后返回string,因此Capital<T extends string>就不會報錯了。
如果對象屬性名采用索引形式,keyof 會返回屬性名的索引類型。
// 示例一
interface T {
[prop: number]: number;
}
// number
type KeyT = keyof T;
// 示例二
interface T {
[prop: string]: number;
}
// string|number
type KeyT = keyof T;
上面的示例二,keyof T返回的類型是string|number,原因是 JavaScript 屬性名為字符串時,包含了屬性名為數(shù)值的情況,因為數(shù)值屬性名會自動轉(zhuǎn)為字符串
如果 keyof 運算符用于數(shù)組或元組類型,得到的結果可能出人意料。
type Result = keyof ['a', 'b', 'c']; // 返回 number | "0" | "1" | "2" // | "length" | "pop" | "push" | ···
上面示例中,keyof 會返回數(shù)組的所有鍵名,包括數(shù)字鍵名和繼承的鍵名。
對于聯(lián)合類型,keyof 返回成員共有的鍵名。
type A = { a: string; z: boolean };
type B = { b: string; z: boolean };
// 返回 'z'
type KeyT = keyof (A | B);
對于交叉類型,keyof 返回所有鍵名。
type A = { a: string; x: boolean };
type B = { b: string; y: number };
// 返回 'a' | 'x' | 'b' | 'y'
type KeyT = keyof (A & B);
// 相當于
keyof (A & B) ≡ keyof A | keyof B
keyof 取出的是鍵名組成的聯(lián)合類型,如果想取出鍵值組成的聯(lián)合類型,可以像下面這樣寫。
type MyObj = {
foo: number,
bar: string,
};
type Keys = keyof MyObj;
type Values = MyObj[Keys]; // number|string
上面示例中,Keys是鍵名組成的聯(lián)合類型,而MyObj[Keys]會取出每個鍵名對應的鍵值類型,組成一個新的聯(lián)合類型,即number|string。
2. keyof運算符的用途
- 往往用于精確表達對象的屬性類型
- 用于屬性映射,即將一個類型的所有屬性逐一映射成其他值
2. in運算符
在js中in用來確定對象是否包含某個屬性名,在ts 類型運算中,in運算符用來取出(遍歷)聯(lián)合類型的每一個成員類型。
type U = 'a'|'b'|'c';
type Foo = {
[Prop in U]: number;
};
// 等同于
type Foo = {
a: number,
b: number,
c: number
};
[Prop in U]表示依次取出聯(lián)合類型U的每一個成員。
3. 方括號運算符
用來取出對象的鍵值類型,比如T[K]會返回對象T的屬性K的類型。
type Person = {
age: number;
name: string;
alive: boolean;
};
// Age 的類型是 number
type Age = Person['age'];
方括號的參數(shù)如果是聯(lián)合類型,那么返回的也是聯(lián)合類型。
type Person = {
age: number;
name: string;
alive: boolean;
};
// number|string
type T = Person['age'|'name'];
// number|string|boolean
type A = Person[keyof Person];
如果訪問不存在的屬性,會報錯。
type T = Person['notExisted']; // 報錯
方括號運算符的參數(shù)也可以是屬性名的索引類型。
type Obj = {
[key:string]: number,
};
// number
type T = Obj[string];
這個語法對于數(shù)組也適用,可以使用number作為方括號的參數(shù)。
// MyArray 的類型是 { [key:number]: string }
const MyArray = ['a','b','c'];
// 等同于 (typeof MyArray)[number]
// 返回 string
type Person = typeof MyArray[number];
上面示例中,MyArray是一個數(shù)組,它的類型實際上是屬性名的數(shù)值索引,而typeof MyArray[number]的typeof運算優(yōu)先級高于方括號,所以返回的是所有數(shù)值鍵名的鍵值類型string。
方括號里面不能有值的運算。
// 示例一 const key = 'age'; type Age = Person[key]; // 報錯 // 示例二 type Age = Person['a' + 'g' + 'e']; // 報錯
上面兩個示例,方括號里面都涉及值的運算,編譯時不會進行這種運算,所以會報錯。
4. extends…?:條件運算符
可以根據(jù)當前類型是否符合某種條件,返回不同的類型。
T extends U ? X : Y
上面式子中的extends用來判斷,類型T是否可以賦值給類型U,即T是否為U的子類型,這里的T和U可以是任意類型。如果T能夠賦值給類型U,表達式的結果為類型X,否則結果為類型Y。
// true type T = 1 extends number ? true : false;
上面示例中,1是number的子類型,所以返回true。
如果需要判斷的類型是一個聯(lián)合類型,那么條件運算符會展開這個聯(lián)合類型。
(A|B) extends U ? X : Y // 等同于 (A extends U ? X : Y) | (B extends U ? X : Y)
上面示例中,A|B是一個聯(lián)合類型,進行條件運算時,相當于A和B分別進行運算符,返回結果組成一個聯(lián)合類型。
如果不希望聯(lián)合類型被條件運算符展開,可以把extends兩側的操作數(shù)都放在方括號里面。
// 示例一 type ToArray<Type> = Type extends any ? Type[] : never; // string[]|number[] type T = ToArray<string|number>; // 示例二 type ToArray<Type> = [Type] extends [any] ? Type[] : never; // (string | number)[] type T = ToArray<string|number>;
上面的示例一,傳入ToArray<Type>的類型參數(shù)是一個聯(lián)合類型,所以會被展開,返回的也是聯(lián)合類型。示例二是extends兩側的運算數(shù)都放在方括號里面,所以傳入的聯(lián)合類型不會展開,返回的是一個數(shù)組。
條件運算符還可以嵌套使用。
type LiteralTypeName<T> = T extends undefined ? "undefined" : T extends null ? "null" : T extends boolean ? "boolean" : T extends number ? "number" : T extends bigint ? "bigint" : T extends string ? "string" : never; // "bigint" type Result1 = LiteralTypeName<123n>; // "string" | "number" | "boolean" type Result2 = LiteralTypeName<true | 1 | 'a'>;
上面示例是一個多重判斷,返回一個字符串的值類型,對應當前類型。
5. infer關鍵字
用來定義泛型里面推斷出來的類型參數(shù),而不是外部傳入的類型參數(shù)。它通常跟條件運算符一起使用,用在extends關鍵字后面的父類型中。
type Flatten<Type> = Type extends Array<infer Item> ? Item : Type;
上面示例中,infer Item表示Item這個參數(shù)是 TypeScript 自己推斷出來的,不用顯式傳入,而Flatten<Type>則表示Type這個類型參數(shù)是外部傳入的。Type extends Array<infer Item>則表示,如果參數(shù)Type是一個數(shù)組,那么就將該數(shù)組的成員類型推斷為Item,即Item是從Type推斷出來的。
一旦使用Infer Item定義了Item,后面的代碼就可以直接調(diào)用Item了。下面是上例的泛型Flatten<Type>的用法。
// string type Str = Flatten<string[]>; // number type Num = Flatten<number>;
上面示例中,第一個例子Flatten<string[]>傳入的類型參數(shù)是string[],可以推斷出Item的類型是string,所以返回的是string。第二個例子Flatten<number>傳入的類型參數(shù)是number,它不是數(shù)組,所以直接返回自身。
如果不用infer定義類型參數(shù),那么就要傳入兩個類型參數(shù)。
type Flatten<Type, Item> = Type extends Array<Item> ? Item : Type;
上面是不使用infer的寫法,每次調(diào)用Flatten的時候,都要傳入兩個參數(shù),就比較麻煩。
下面的例子使用infer,推斷函數(shù)的參數(shù)類型和返回值類型。
type ReturnPromise<T> = T extends (...args: infer A) => infer R ? (...args: A) => Promise<R> : T;
上面示例中,如果T是函數(shù),就返回這個函數(shù)的 Promise 版本,否則原樣返回。infer A表示該函數(shù)的參數(shù)類型為A,infer R表示該函數(shù)的返回值類型為R。
如果不使用infer,就不得不把ReturnPromise<T>寫成ReturnPromise<T, A, R>,這樣就很麻煩,相當于開發(fā)者必須人肉推斷編譯器可以完成的工作。
下面是infer提取對象指定屬性的例子。
type MyType<T> =
T extends {
a: infer M,
b: infer N
} ? [M, N] : never;
// 用法示例
type T = MyType<{ a: string; b: number }>;
// [string, number]上面示例中,infer提取了參數(shù)對象的屬性a和屬性b的類型。
下面是infer通過正則匹配提取類型參數(shù)的例子。
type Str = 'foo-bar';
type Bar = Str extends `foo-${infer rest}` ? rest : never // 'bar'上面示例中,rest是從模板字符串提取的類型參數(shù)。
6. is運算符
函數(shù)返回布爾值時,可以使用is運算符,來限定返回值與參數(shù)之間的關系。
is運算符用來描述返回值是true還是false。
function isFish(
pet: Fish|Bird
):pet is Fish {
return (pet as Fish).swim !== undefined;
}
上面示例中,函數(shù)isFish()的返回值類型為pet is Fish,表示如果參數(shù)pet類型為Fish,則返回true,否則返回false。
is運算符總是用于描述函數(shù)的返回值類型,寫法采用parameterName is Type的形式,即左側為當前函數(shù)的參數(shù)名,右側為某一種類型。它返回一個布爾值,表示左側參數(shù)是否屬于右側的類型。
is運算符可以用于類型保護。
function isCat(a:any): a is Cat {
return a.name === 'kitty';
}
let x:Cat|Dog;
if (isCat(x)) {
x.meow(); // 正確,因為 x 肯定是 Cat 類型
}上面示例中,函數(shù)isCat()的返回類型是a is Cat,它是一個布爾值。后面的if語句就用這個返回值進行判斷,從而起到類型保護的作用,確保x是 Cat 類型,從而x.meow()不會報錯(假定Cat類型擁有meow()方法)
is運算符還有一種特殊用法,就是用在類(class)的內(nèi)部,描述類的方法的返回值。
class Teacher {
isStudent():this is Student {
return false;
}
}
class Student {
isStudent():this is Student {
return true;
}
}上面示例中,isStudent()方法的返回值類型,取決于該方法內(nèi)部的this是否為Student對象。如果是的,就返回布爾值true,否則返回false。
注意,this is T這種寫法,只能用來描述方法的返回值類型,而不能用來描述屬性的類型。
7. 模板字符串
ts可以使用模板字符串構建類型,模板字符串最大的特點就是內(nèi)部可以引用其他類型。
type World = "world";
// "hello world"
type Greeting = `hello ${World}`;
上面示例中,類型Greeting是一個模板字符串,里面引用了另一個字符串類型world,因此Greeting實際上是字符串hello world
模板字符串可以引用的類型一共6種,分別是 string、number、bigint、boolean、null、undefined。引用這6種以外的類型會報錯。
模板字符串里面引用的類型,如果是一個聯(lián)合類型,那么它返回的也是一個聯(lián)合類型,即模板字符串可以展開聯(lián)合類型。
type T = 'A'|'B';
// "A_id"|"B_id"
type U = `${T}_id`;
上面示例中,類型U是一個模板字符串,里面引用了一個聯(lián)合類型T,導致最后得到的也是一個聯(lián)合類型。
如果模板字符串引用兩個聯(lián)合類型,它會交叉展開這兩個類型。
type T = 'A'|'B';
type U = '1'|'2';
// 'A1'|'A2'|'B1'|'B2'
type V = `${T}${U}`;
上面示例中,T和U都是聯(lián)合類型,各自有兩個成員,模板字符串里面引用了這兩個類型,最后得到的就是一個4個成員的聯(lián)合類型。
8. satisfies運算符
satisfies 是 TypeScript 4.9 版本中引入的一個新的運算符,它可以讓你檢查一個給定的類型是否滿足一個特定的接口或條件。換句話說,它可以確保一個類型具有一個特定接口所要求的所有屬性和方法。它是一種保證一個變量符合一個類型定義的方式。
satisfies 運算符的語法是在一個值后面加上 satisfies,然后跟上一個類型的名稱:
someValue satisfies SomeType;
satisfies 運算符有以下優(yōu)點:
- 它可以讓你在不改變值的原始類型的情況下,對值的類型進行驗證和約束(與 : 注解不同)。
- 它可以讓你保留值的最具體的類型信息,而不是將其擴展為更一般的類型(與默認類型推斷不同)。
舉個例子,假設我們有一個 Vibe 接口,它定義了一個 mood 屬性,其類型為 "happy" | "sad"。我們可以用 satisfies 運算符來保證我們創(chuàng)建的 vibe 對象的 mood 屬性只能是這兩個字符串字面量之一,同時還能保持 mood 屬性的具體值為 "happy"。
interface Vibe {
mood: "happy" | "sad";
}
const vibe = { mood: "happy" } satisfies Vibe;
vibe.mood; // "happy"到此這篇關于TypeScript中的類型運算符實現(xiàn)的文章就介紹到這了,更多相關TypeScrip 類型運算符內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
JavaScript使用addEventListener添加事件監(jiān)聽用法實例
這篇文章主要介紹了JavaScript使用addEventListener添加事件監(jiān)聽的方法,實例分析了addEventListener方法的相關使用技巧,需要的朋友可以參考下2015-06-06
JS生態(tài)系統(tǒng)加速模塊解析賦能性能優(yōu)化探索
這篇文章主要為大家介紹了JS生態(tài)系統(tǒng)加速模塊解析賦能性能優(yōu)化探索,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2024-01-01
微信小程序canvas.drawImage完全顯示圖片問題的解決
這篇文章主要介紹了微信小程序canvas.drawImage完全顯示圖片問題的解決,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-11-11
js?通過Object.defineProperty()?定義和控制對象屬性
這篇文章主要介紹了js?通過Object.defineProperty()?定義和控制對象屬性,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的朋友可以參考一下2022-08-08

