如何建立高質量的TypeScript宣告檔案(七)
該做什麼和不該做什麼
一般型別
數字,字串,布林值和物件
不要使用Number,String,Boolean或Object型別。 這些型別指的是在JavaScript程式碼中幾乎從不正確使用的非原始盒裝物件。
/* WRONG */
function reverse(s: String): String;
請使用型別number,string和boolean。
/* OK */
function reverse(s: string): string;
而不是Object,使用非基本物件型別(在TypeScript 2.2中新增)。
泛型
不要使用不使用其型別引數的泛型型別。在TypeScript FAQ頁面中檢視更多詳細資訊。
回撥型別
返回回撥型別
不要將返回型別any用於其值將被忽略的回撥:
/* WRONG */
function fn(x: () => any) {
x();
}
對於其值將被忽略的回撥,請使用返回型別void:
/* OK */
function fn(x: () => void) {
x();
}
原因:使用void更安全,因為它可以防止您以未經檢查的方式意外使用x的返回值:
function fn(x: () => void) { var k = x(); // oops! meant to do something else k.doSomething(); // error, but would be OK if the return type had been 'any' }
回撥中的可選引數
除非你真的是這樣說,否則不要在回撥中使用可選引數:
/* WRONG */
interface Fetcher {
getObject(done: (data: any, elapsedTime?: number) => void): void;
}
這具有非常具體的含義:完成的回撥可以使用1個引數呼叫,也可以使用2個引數呼叫。作者可能打算說回撥可能不關心elapsedTime引數,但是沒有必要使引數可選來完成這一點 - 提供一個接受較少引數的回撥總是合法的。
寫回調引數是非可選的:
/* OK */ interface Fetcher { getObject(done: (data: any, elapsedTime: number) => void): void; }
過載和回撥
不要編寫僅在回撥函式引數上不同的單獨過載:
/* WRONG */
declare function beforeAll(action: () => void, timeout?: number): void;
declare function beforeAll(action: (done: DoneFn) => void, timeout?: number): void;
應該只使用最大引數個數寫一個過載:
/* OK */
declare function beforeAll(action: (done: DoneFn) => void, timeout?: number): void;
原因:忽略引數的回撥總是合法的,因此不需要更短的過載。首先提供較短的回撥允許傳入錯誤輸入的函式,因為它們匹配第一個過載。
函式過載
順序
不要在更具體的過載之前放置更多的一般過載:
/* WRONG */
declare function fn(x: any): any;
declare function fn(x: HTMLElement): number;
declare function fn(x: HTMLDivElement): string;
var myElem: HTMLDivElement;
var x = fn(myElem); // x: any, wat?
通過在更具體的簽名之後放置更一般的簽名來對過載進行排序:
/* OK */
declare function fn(x: HTMLDivElement): string;
declare function fn(x: HTMLElement): number;
declare function fn(x: any): any;
var myElem: HTMLDivElement;
var x = fn(myElem); // x: string, :)
原因:TypeScript在解析函式呼叫時選擇第一個匹配的過載。當較早的過載比較晚的過載“更一般”時,後一個過載是有效隱藏的,不能被呼叫。
使用可選引數
不要寫幾個僅在尾隨引數上有所不同的過載:
/* WRONG */
interface Example {
diff(one: string): number;
diff(one: string, two: string): number;
diff(one: string, two: string, three: boolean): number;
}
儘可能使用可選引數:
/* OK */
interface Example {
diff(one: string, two?: string, three?: boolean): number;
}
請注意,只有當所有過載具有相同的返回型別時,才會發生此摺疊。
原因:這有兩個重要原因。
TypeScript通過檢視是否可以使用源的引數呼叫目標的任何簽名來解析簽名相容性,並允許使用無關的引數。例如,只有在使用可選引數正確編寫簽名時,此程式碼才會公開錯誤:
function fn(x: (a: string, b: number, c: number) => void) { }
var x: Example;
// When written with overloads, OK -- used first overload
// When written with optionals, correctly an error
fn(x.diff);
第二個原因是消費者使用TypeScript的“嚴格空檢查”功能。由於未指定的引數在JavaScript中顯示為未定義,因此將顯式的undefined傳遞給帶有可選引數的函式通常很好。例如,在嚴格的空值下,此程式碼應該是正常的:
使用聯合型別
不要只在一個引數位置寫入因型別不同的過載:
/* WRONG */
interface Moment {
utcOffset(): number;
utcOffset(b: number): Moment;
utcOffset(b: string): Moment;
}
儘可能使用聯合型別:
/* OK */
interface Moment {
utcOffset(): number;
utcOffset(b: number|string): Moment;
}
請注意,我們在這裡沒有使b可選,因為簽名的返回型別不同。
原因:這對於將“值”傳遞給函式的人來說非常重要:
function fn(x: string): void;
function fn(x: number): void;
function fn(x: number|string) {
// When written with separate overloads, incorrectly an error
// When written with union types, correctly OK
return moment().utcOffset(x);
}