交叉類型
將多個類型合並成一個類型,取兩個類型的並集。與繼承的區別是,繼承可以有自己的屬性,而交叉沒有。
interface DogInterface { run():void } interface CatInterface { jump():void } let pet: DogInterface & CatInterface = { // 看上去和接口多繼承很像,但有一點區別。繼承可以有自己的屬性,交叉不行。 run(){}, jump(){}, };
聯合類型
聲明的類型並不確定,可以是多個類型中的一個。
let a: number | string = "a"; // 類型限定 let b: "a" | "b" | "c"; // 限定取值 let c: 1 | 2 | 3 | "v"; // 限定取值
可區分的類型保護:
// 現在有兩種形狀,area函數用來計算每種形狀的面積。 interface Square{ kind: "square"; size: number; } interface Rectangle{ kind: "rectangle", width: number, height: number, } type Shape = Square | Rectangle; function area(s: Shape) { switch (s.kind) { case "square": return s.size * s.size; // 此區塊內,確保只有size屬性 case "rectangle": return s.height * s.width; } } console.log(area({kind:"square",size:10})); // 100 // 現在要添加一個形狀:圓形。需要定義接口Circle、為Shape添加聯合類型Circle,然后為area函數內增加一個case。但是,如果我們忘了修改area函數,會發生什么? interface Circle{ kind: "circle", r: number, } type Shape = Square | Rectangle | Circle; console.log(area({kind:"circle",r:10})); // undefined,這里並不報錯,並不符合我們的預期。我們希望bug能夠及時暴露出來,增加程序的穩定性。 做如下改動: function area(s: Shape) { switch (s.kind) { case "square": return s.size * s.size; case "rectangle": return s.height * s.width; case "circle": return Math.PI * s.r; default: return ((e: any)=>{throw new Error(`沒有定義 ${s} 的面積計算方式`)})(s) // 這一步很重要,一定要在這里拋出異常 } }
索引類型
當我們使用不存在的索引時,會返回undefined,沒有約束(如下代碼)。因此我們需要有對索引的約束。
let obj = { a: 1, b: 2, c: 3, }; function getValue(obj: any,keys: string[]){ return keys.map(key => obj[key]); } console.log(getValue(obj,["a","b"])); console.log(getValue(obj,["c","f"])); // 會發現,'f'對應的輸出是undefined,沒有約束,需要用到索引類型
下面使用索引類型:
function getValue<T,K extends keyof T>(obj: T, keys: K[]): T[K][] { // T[k][]表示,返回值必須是obj中的值組成的列表 return keys.map(key => obj[key]); // 此時keys中的元素只能是obj中的鍵 } console.log(getValue(obj,["a","b"])); console.log(getValue(obj,["c","f"])); // 這時就會報錯,有了約束 'f' is not in "a" | "b" | "c"
我們來解釋一下:
這里會用到兩個操作符,查詢操作符 keyof T 和 訪問操作符 T[k](看下面示例)。<T, K extends keyof T> 用到了泛型約束,表示K所約束的參數的值只能是T所約束參數數據中的“鍵”。
// keyof T interface Obj{ a: number; b: string; } let key: keyof Obj; // 此時key表示 'a' | 'b' // T[k] let value: Obj['a'] // number
映射類型
可以從舊的類型生成新的類型。比如,將接口中的所有成員變成只讀、可選。
TS內置了很多映射類型。
interface Obj{ a: string; b: number; c: boolean; } // 將Obj接口中每個成員變成只讀屬性,生成一個新的接口。 type ReadonlyObj = Readonly<Obj>; // Readonly是TS內置的映射類型,下同 // Readonly實現原理,利用了索引類型的操作方法 type Readonly<T> = { readonly [P in keyof T]: T[P]; }
再如:
// 將所有屬性變成可選 type PartialObj = Partial<Obj>; // Partial實現原理 type Partial<T> = { [P in keyof T]?: T[P]; } // 獲取原類型的子集 type PickObj = Pick<Obj,'a'|'b'>; // 等同於 interface PickObj { a: string, b: number } // 將原類型當做新類型的成員 type RecordObj = Record<'x'|'y',Obj>; // 等同於 interface RecordObj { x: Obj, y: Obj, }
沙發
條件類型
條件類型指由表達式所決定的類型。條件類型使類型具有了不唯一性,增加了語言的靈活性。
例如:
T extends U ? X : Y 表示如果類型T可以被賦值給類型U,name結果就賦予X類型,否則賦予Y類型
再如:
type TypeName<T> = T extends string ? string : T extends number ? number : T extends boolean ? boolean : T extends undefined ? undefined : T extends Function ? Function : object; type T1 = TypeName<string>; // T1為字符串類型 type T2 = TypeName<string[]>; // T2為object類型 type T3 = TypeName<Function>; // T3為function類型 type T4 = TypeName<string | string[]>; // T4為 string和object的聯合類型
可以用來做什么?
(A | B) extends U ? X : Y 解析為(A extends U ? X : Y) | (B extends U ? X : Y)
可以利用這一特性做類型的過濾,例如:
type Diff<T,U> = T extends U ? never : T; type T5 = Diff< 'a'|'b'|'c', 'a'|'e' >; // 作用是過濾掉第一個參數中的'a' 。T5為 'b' | 'c'聯合類型 解析過程: Diff<'a', 'a'|'e'> | Diff<'b', 'a'|'e'> | Diff<'c', 'a'|'e'> never | 'b' | 'c' 'b' | 'c'
TS內置的條件類型:
Exclude<T, U> // 從T中剔除可以賦值給U的類型,相當於上面例子中的Diff Extract<T, U> // 提取T中可以賦值給U的類型。 NonNullable<T> // 從T中剔除null和undefined。 ReturnType<T> // 獲取函數返回值類型。 InstanceType<T> // 獲取構造函數類型的實例類型。