?文章將討論處理類型的幾個高級模式,包括模擬名義類型的類型烙印、利用條件類型的分配性質在類型層面操作類型,以及安全地擴展原型。
1 函數類型
TS在推導元組的類型時會放寬要求,推導出的結果盡量寬泛,不在乎元組的長度和各位置的類型:
let a = [“hello”,false,1]; // (string | boolean | number)[]
我們有時希望推導結果更嚴格些,比如上面的類型應該為 [string,boolean,number]。我們可以使用類型斷言或者as const來收窄推導結果,我們還可以自定義函數,來實現元組類型收窄:
function tuple<T extends unknown[]>(...t: T) {return t;
}
let t1 = tuple(2,false); // [number,boolean]
1.1 自定義類型防護措施
開發中,我們時常需要判斷對象的類型,我們會寫一個函數來進行判斷:
function isString(input: unknown) {return typeof input === 'string';
}
但是這個函數在某些場景中,可能會達不到我們的預期效果:
function fun(input: string | number) {if (isString(input)) {input.toUpperCase();//報錯 Property toUpperCase does not exist on type string | number// Property toUpperCase does not exist on type number}
}
這是因為TS類型細化能力有限,只能細化當前作用域中變量的類型,即上面代碼中,TS只能在isString函數中來保證input是個string類型。
TS內置特性:類型防護措施。這是typeof和instanceof細化類型的背后機制。我們通過自定義類型防護措施來實現類型的細化在作用域之間轉移:
function isString(a: unknown):a is string {return typeof a === 'string';
}
function fun(input: number | string) {if (isString(input)) {input.toUpperCase();}
}
2 條件類型
條件類型是聲明一個依賴類型U和V的類型T,如果 U <: V,就把T賦值給A,否則賦值給B。例如:
type IsString<T> = T extends string ? true : false。
type A = isString<number>; // false
type A = isString<string>; // true
條件類型使用廣泛,可以使用類型的地方幾乎都能使用條件類型。
type WithoutArrayType<T,U> = T extends U ? never : T[];
type T1 = WithoutArrayType<string | number | boolean, boolean>; // string[] | number[]
2.1 infer 關鍵字聲明泛型
在條件類型中不能適用尖括號(<T>)來聲明范型,而是使用infer關鍵字。
type ElementType<T> = T extends unknown[] ? T[number] : T;
type ElementType2<T> = T extends (infer U)[] ? U : T; // 等效上面type T1 = ElementType<number[]>; // number
type T2 = ElementType2<string[][]>; //string[]
2.2 內置的條件類型
Exclude<T,U>: 返回在T中而不在U中的類型。
type ExcludeType = Exclude<string | number | boolean, number>; // string | boolean
Extract<T,U>: 返回T中可賦值給U的類型。
type ExtractType = Extract< number | boolean, string | number | 'x'>; // number
NonNullable<T>: 從T中排出null和undefined。
type A = { a? :number,b: string | null};
type aType = NonNullable<A["a"]>; // number
type A1 = NonNullable<A>; // A & {}let a1:A1 = {a:1,b:1};
let a2:A1 = {}; // Property b is missing in type {} but required in type A
let a3:A1 = {b:null};
ReturnType<F>: 計算函數的返回類型(不適用泛型和重載的函數)。
type Fun = () => string;
type T1 = ReturnType<Fun>; // string
InstanceType<C>: 返回類構造方法的實例類型。
type CustomClass = {new(): C}
type C = {name:"user"};
type T = InstanceType<CustomClass>; // {name: "user"}
3 斷言
在沒有足夠時間把所有類型都規劃好,但是我們希望TS不要因為這個而讓我們程序運行不起來,我們可以使用斷言的方式(但要盡量少用,如果大量依賴這些,說明項目需要重構了)。
1)類型斷言。有兩種方式:as 和尖括號。
function fun(input: string | number) {let str = input as string;// stringlet str2 = <string> input; // string
}
2)非空斷言。
type Student = {id?: number};
let s:Student = {};
let s2: Student = {id: 2};
function fun2(id: number) {}
fun2(s.id!);
fun2(s2.id!);
fun2(s2.id); // 報錯 Argument of type 'number | undefined' is not assignable to parameter of type 'number'.
3)明確賦值斷言。TS通過明確賦值檢查確保使用變量時已經為其賦值。
我們在定義一個變量時,如果在沒有為其賦值而使用它,那么TS將報錯。
class Student{name: stringshowName() {console.log(this.name);}
}let student = new Student();
student.showName(); // 報錯? Property 'name' has no initializer and is not definitely assigned in the constructor.
明確賦值,是指在變量名后面加個”!“符號,來告訴TS,我已經確保,在使用這個變量之前,我已經為它賦值。
class Student{name!: stringshowName() {console.log(this.name);}
}let student = new Student();
student.showName(); // undefined
4 名義類型
TS 是結構化類型不是名義類型系統,即TS只根據類型結構來判斷類型而不是類型名稱。而在Java中,是根據類名來確定類型的。比如Man 和 People,在TS中,如果這兩個類型的結構是一樣的,那么它們是同一個類型,而在Java中,因為它們名字不同,就注定它們不是同一個類型。
雖然在TS中,通過結構類型給予了開發者很多方便,但是有時名義類型能發揮的作用是結構類型很難替代的:
定義一個函數,要求參數為一個StudentID類型的字符串。而這個StudentID本質上也是字符串類型。
我們可以使用類型烙印技術模擬實現。
4.1 類型烙印
類型烙印即給特定類型一個唯一結構,來讓它從其他相似類型結構中區別開。
type StudentID = string & {readonly brand: unique symbol};
function StudentID(id:string) {return id as StudentID;
}function showStudentId(id: StudentID) {}let id = StudentID("123"); // StudentID
showStudentId(id);
showStudentId("123"); // 報錯? Argument of type string is not assignable to parameter of type StudentID
//Type string is not assignable to type { readonly brand: unique symbol; }
類型烙印降低了運行時的開銷,是一種編譯時結構。
多數應用沒必要使用這個,不過,對于大型應用或者處理容易混淆的類型時,帶烙印的類型可以極大地提升程序的安全性。