交叉類型
將多個類型合並成一個類型,取兩個類型的並集。與繼承的區別是,繼承可以有自己的屬性,而交叉沒有。
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> // 獲取構造函數類型的實例類型。
