新聞中心
// 非對象類型交叉運算
type N0 = string & number;
type N1 = any & 1;
type N2 = any & never;
// 對象類型交叉運算
type A = { kind: 'a', foo: string };
type B = { kind: 'b', foo: number };
type C = { kind: 'c', foo: number };
type AB = A & B;
type BC = B & C;
// 函數類型交叉運算
type F1 = (a: string, b: string) => void;
type F2 = (a: number, b: number) => void;
type Fn = F1 & F2
在學習 TypeScript 的過程中,你可以把類型理解成一系列值的集合。比如,你可以把數字類型看作是所有數字的集合,1.0、68 就屬于這個集合中,而 "阿寶哥" 就不屬于這個集合,因為它屬于字符串類型。

新源網站制作公司哪家好,找創(chuàng)新互聯!從網頁設計、網站建設、微信開發(fā)、APP開發(fā)、響應式網站等網站項目制作,到程序開發(fā),運營維護。創(chuàng)新互聯自2013年創(chuàng)立以來到現在10年的時間,我們擁有了豐富的建站經驗和運維經驗,來保證我們的工作的順利進行。專注于網站建設就選創(chuàng)新互聯。
同樣,對于對象類型來說,我們也可以把它理解成對象的集合。比如以上代碼中 Point 類型表示含有 x 和 y 屬性,且屬性值的類型都是 number 類型對象的集合。而 Named 類型表示含有 name 屬性且屬性值的類型是 string 類型對象的集合。
interface Point {
x: number;
y: number;
}
interface Named {
name: string;
}在集合論中,假設 A,B 是兩個集合,由所有屬于集合 A 且屬于集合 B 的元素所組成的集合,叫做集合 A 與集合 B 的交集。
當我們對 Point 類型和 Named 類型進行交集運算,就會產生新的類型。該類型中所包含的對象既屬于 Point 類型,又屬于 Named 類型。
在 TypeScript 中為我們提供了交叉運算符,來實現對多種類型進行交叉運算,所產生的新類型也被稱為交叉類型。
下面我們來簡單介紹一下交叉運算符,該運算符滿足以下這些特性:
- 唯一性:A & A 等價于 A
- 滿足交換律:A & B 等價于 B & A
- 滿足結合律:(A & B) & C 等價于 A & (B & C)
- 父類型收斂:如果 B 是 A 的父類型,則 A & B 將被收斂成 A 類型
type A0 = 1 & number; // 1
type A1 = "1" & string; // "1"
type A2 = true & boolean; // true
type A3 = any & 1; // any
type A4 = any & boolean; // any
type A5 = any & never; // never
在以上代碼中,any 類型和 never 類型比較特殊。除了 never 類型之外,任何類型與 any 類型進行交叉運算的結果都是 any 類型。
介紹完交叉運算符之后,我們來看一下對 Point 類型和 Named 類型進行交叉運算后,將產生什么樣的類型?
interface Point {
x: number;
y: number;
}
interface Named {
name: string;
}
type NamedPoint = Point & Named
// {
//. x: number;
//. y: number;
//. name: string;
//. }在以上代碼中,新產生的 NamedPoint 類型將會同時包含 x、y 和 name 屬性。但如果進行交叉運算的多個對象類型中,包含相同的屬性但屬性的類型不一致結果又會是怎樣呢?
interface X {
c: string;
d: string;
}
interface Y {
c: number;
e: string
}
type XY = X & Y;
type YX = Y & X;在以上代碼中,接口 X 和接口 Y 都含有一個相同的 c 屬性,但它們的類型不一致。對于這種情況,此時 XY 類型或 YX 類型中 c 屬性的類型是不是可以是 string 或 number 類型呢?下面我們來驗證一下:
let p: XY = { c: "c", d: "d", e: "e" }; // Error
let q: YX = { c: 6, d: "d", e: "e" }; // Error為什么接口 X 和接口 Y 進行交叉運算后,c 屬性的類型會變成 never 呢?這是因為運算后 c 屬性的類型為 string & number,即 c 屬性的類型既可以是 string 類型又可以是 number 類型。很明顯這種類型是不存在的,所以運算后 c 屬性的類型為 never 類型。
在前面示例中,剛好接口 X 和接口 Y 中 c 屬性的類型都是基本數據類型。那么如果不同的對象類型中含有相同的屬性,且屬性類型是非基本數據類型的話,結果又會是怎樣呢?我們來看個具體的例子:
interface D { d: boolean; }
interface E { e: string; }
interface F { f: number; }
interface A { x: D; }
interface B { x: E; }
interface C { x: F; }
type ABC = A & B & C;
let abc: ABC = { // Ok
x: {
d: true,
e: '阿寶哥',
f: 666
}
};由以上結果可知,在對多個類型進行交叉運算時,若存在相同的屬性且屬性類型是對象類型,那么屬性會按照對應的規(guī)則進行合并。
但需要注意的是,在對對象類型進行交叉運算的時候,如果對象中相同的屬性被認為是可辨識的屬性,即屬性的類型是字面量類型或字面量類型組成的聯合類型,那么最終的運算結果將是 never 類型:
type A = { kind: 'a', foo: string };
type B = { kind: 'b', foo: number };
type C = { kind: 'c', foo: number };
type AB = A & B; // never
type BC = B & C; // never在以上代碼中,A、B、C 三種對象類型都含有 kind 屬性且屬性的類型都是字符串字面量類型,所以 AB 類型和 BC 類型最終都是 never 類型。接下來,我們來繼續(xù)看個例子:
type Foo = {
name: string,
age: number
}
type Bar = {
name: number,
age: number
}
type Baz = Foo & Bar
// {
//. name: never;
//. age: number;
// }在以上代碼中,Baz 類型是含有 name 屬性和 age 屬性的對象類型,其中 name 屬性的類型是 never 類型,而 age 屬性的類型是 number 類型。
但如果把 Foo 類型中 name 屬性的類型改成 boolean 類型的話,Baz 類型將會變成 never 類型。這是因為 boolean 類型可以理解成由 true 和 false 字面量類型組成的聯合類型。
type Foo = {
name: boolean, // true | false
age: number
}
type Bar = {
name: number,
age: number
}
type Baz = Foo & Bar // never其實除了對象類型可以進行交叉運算外,函數類型也可以進行交叉運算:
type F1 = (a: string, b: string) => void;
type F2 = (a: number, b: number) => void;
let f: F1 & F2 = (a: string | number, b: string | number) => { };
f("hello", "world"); // Ok
f(1, 2); // Ok
f(1, "test"); // Error
對于以上代碼中的函數調用語句,只有 f(1, "test") 的調用語句會出現錯誤,其對應的錯誤信息如下:
沒有與此調用匹配的重載。
第 1 個重載(共 2 個),“(a: string, b: string): void”,出現以下錯誤。
類型“number”的參數不能賦給類型“string”的參數。
第 2 個重載(共 2 個),“(a: number, b: number): void”,出現以下錯誤。
類型“string”的參數不能賦給類型“number”的參數。ts(2769)
根據以上的錯誤信息,我們可以了解到 TypeScript 編譯器會利用函數重載的特性來實現不同函數類型的交叉運算,要解決上述問題,我們可以在定義一個新的函數類型 F3,具體如下:
type F1 = (a: string, b: string) => void;
type F2 = (a: number, b: number) => void;
type F3 = (a: number, b: string) => void;
let f: F1 & F2 & F3 = (a: string | number, b: string | number) => { };
f("hello", "world"); // Ok
f(1, 2); // Ok
f(1, "test"); // Ok
掌握了交叉類型之后,在結合往期文章中介紹的映射類型,我們就可以根據工作需要實現一些自定義工具類型了。比如實現一個 PartialByKeys 工具類型,用于把對象類型中指定的 keys 變成可選的。
type User = {
id: number;
name: string;
age: number;
}
type PartialByKeys = Simplify<{
[P in K]?: T[P]
} & Pick>>
type U1 = PartialByKeys
type U2 = PartialByKeys 那么如果讓你實現一個 RequiredByKeys 工具類型,用于把對象類型中指定的 keys 變成必填的,你知道怎么實現么?知道答案的話,你喜歡以這種形式學 TS 么?
網站名稱:同事看完這幾道題,發(fā)現 TS 交叉類型竟還沒入門!
標題路徑:http://m.5511xx.com/article/dpiepse.html


咨詢
建站咨詢
