假設有這樣的一個函式,你會怎麼來宣告他的型別呢?
function add(a,b){
return a+b;
}
add
函式可能有兩種情況:
- 引數
a、b
為number
型別,返回值為number
型別 - 引數
a、b
為string
型別,返回值為string
型別
使用函式過載能解決嗎?
首先,你可能會通過宣告多個函式型別,來實現對add
函式的過載宣告。
function add(a: string, b: string): string;
function add(a: number, b: number): number;
function add(a: any, b: any) {
return a + b;
}
add(1, 2); //function add(a: number, b: number): number
add('x', 'y'); //function add(a: string, b: string): string
這種方法顯然不錯,但是有一個小問題:當傳入的引數型別是number|string
時,會出現意想不到的型別錯誤。
let a:string|number;
add(a,a);
/*
第 1 個過載(共 2 個),“(a: string, b: string): string”,出現以下錯誤。
型別“string | number”的引數不能賦給型別“string”的引數。
不能將型別“number”分配給型別“string”。
第 2 個過載(共 2 個),“(a: number, b: number): number”,出現以下錯誤。
型別“string | number”的引數不能賦給型別“number”的引數。
不能將型別“string”分配給型別“number”。
*/
這是因為當我們使用函式過載時,TypeScript是使用這些過載來逐個比對的,直到匹配到合適的型別過載。但是顯然,我們宣告的兩種過載中的變數型別,number
和string
都與number|string
不匹配,所以出現了型別錯誤。
使用泛型怎麼樣呢?
然後,你還可能會想到使用泛型來宣告型別,以便構建一種通用的模式。
function add<T extends number | string>(a: T, b: T): T;
function add(a: any, b: any) {
return a + b;
}
const a:number = 0;
const b:string='str';
add(a, a);//function add<number>(a: number, b: number): number
add(b, b);//function add<string>(a: string, b: string): string
這個方法看起來也很好,但是同樣有一個小問題:當傳入字面量型別的引數時,該引數的型別會被認為和引數的值相同的型別。例如:
add(1,2);//function add<1 | 2>(a: 1 | 2, b: 1 | 2): 1 | 2
引數1
的type
是1
,而非number
,就如同我們宣告瞭 type T=1|2
;一樣,函式型別宣告中的泛型T既要滿足第一個引數1
,又要滿足第二個引數2
,所以T的型別成為了1|2
;同樣的,如果傳入的變數a、b
沒有顯式的型別宣告number
和string
,也會出現這個問題。
條件型別可能會更好
其實,在這種函式需要過載的時候使用條件型別,可能會有更好的效果。
function add<T extends number | string>(
a: T,
b: T
): T extends number ? number : string;
function add(a: any, b: any) {
return a + b;
}
add(1,2);//function add<1 | 2>(a: 1 | 2, b: 1 | 2): number
let a:number|string;
add(a, a);//function add<string | number>(a: string | number, b: string | number): string | number
這種方式是先通過泛型的方法獲取到引數的型別,在使用條件型別進行判斷,從而得出正確的型別。
這種方法相較於前兩種方法寫起來更復雜,但是型別會更加準確。在函式需要型別過載時,不妨考慮一下使用條件型別來實現。