一般類型
數字
、字串
、布林
、符號
和物件
❌ 請勿使用類型數字
、字串
、布林
、符號
或物件
。這些類型是指非原始的封裝物件,在 JavaScript 程式碼中幾乎從未使用得恰當。
ts
/* WRONG */function reverse(s: String): String;
✅ 請使用類型number
、string
、boolean
和symbol
。
ts
/* OK */function reverse(s: string): string;
請勿使用物件
,請使用非原始的object
類型(新增於 TypeScript 2.2)。
泛型
❌ 請勿使用未套用其類型參數的泛型類型。請參閱 TypeScript 常見問題解答頁面 以取得更多詳細資訊。
any
❌ 請勿使用any
作為類型,除非您正在將 JavaScript 專案移轉至 TypeScript。編譯器實際上將any
視為「請關閉此項目的類型檢查」。這類似於在變數的每個使用位置加上@ts-ignore
註解。當您首次將 JavaScript 專案移轉至 TypeScript 時,這可能非常有幫助,因為您可以將尚未移轉的項目設定為any
類型,但在完整的 TypeScript 專案中,您會停用使用它的程式任何部分的類型檢查。
在不知道要接受什麼類型,或想接受任何東西(因為會盲目傳遞而不會與之互動)時,可以使用 unknown
。
回呼類型
回呼的傳回類型
❌ 不要 對會忽略其值的回呼使用 any
傳回類型
ts
/* WRONG */function fn(x: () => any) {x();}
✅ 要 對會忽略其值的回呼使用 void
傳回類型
ts
/* OK */function fn(x: () => void) {x();}
❔ 原因: 使用 void
較安全,因為它可以防止意外以未檢查的方式使用 x
的傳回值
ts
function fn(x: () => void) {var k = x(); // oops! meant to do something elsek.doSomething(); // error, but would be OK if the return type had been 'any'}
回呼中的選用參數
❌ 不要 在回呼中使用選用參數,除非你真的有此意
ts
/* WRONG */interface Fetcher {getObject(done: (data: unknown, elapsedTime?: number) => void): void;}
這有非常具體的意義:done
回呼可能會使用 1 個參數呼叫,也可能會使用 2 個參數呼叫。作者可能想說回呼可能不關心 elapsedTime
參數,但不需要將參數設為選用以達成此目的 — 始終可以提供接受較少參數的回呼。
✅ 請將回呼參數寫成非選用
ts
/* OK */interface Fetcher {getObject(done: (data: unknown, elapsedTime: number) => void): void;}
過載和回呼
❌ 請勿撰寫僅在回呼 arity 上有差異的個別過載
ts
/* WRONG */declare function beforeAll(action: () => void, timeout?: number): void;declare function beforeAll(action: (done: DoneFn) => void,timeout?: number): void;
✅ 請使用最大 arity 撰寫單一過載
ts
/* OK */declare function beforeAll(action: (done: DoneFn) => void,timeout?: number): void;
❔ 原因:回呼總是合法地忽略參數,因此不需要較短的過載。優先提供較短的回呼允許傳遞輸入類型錯誤的函式,因為它們符合第一個過載。
函式過載
排序
❌ 請勿將較通用的過載置於較具體的過載之前
ts
/* WRONG */declare function fn(x: unknown): unknown;declare function fn(x: HTMLElement): number;declare function fn(x: HTMLDivElement): string;var myElem: HTMLDivElement;var x = fn(myElem); // x: unknown, wat?
✅ 請依序將較通用的簽章置於較具體的簽章之後,來排序過載
ts
/* OK */declare function fn(x: HTMLDivElement): string;declare function fn(x: HTMLElement): number;declare function fn(x: unknown): unknown;var myElem: HTMLDivElement;var x = fn(myElem); // x: string, :)
❔ 原因:TypeScript 在解析函式呼叫時會選擇第一個符合的過載。當較早的過載「較通用」於較晚的過載時,較晚的過載實際上會被隱藏且無法呼叫。
使用選用參數
❌ 請勿撰寫僅在尾端參數不同的多個重載
ts
/* WRONG */interface Example {diff(one: string): number;diff(one: string, two: string): number;diff(one: string, two: string, three: boolean): number;}
✅ 請盡可能使用可選參數
ts
/* OK */interface Example {diff(one: string, two?: string, three?: boolean): number;}
請注意,只有當所有重載具有相同的回傳類型時,才應執行此合併。
❔ 原因:這很重要,原因有兩個。
TypeScript 會透過查看目標的任何簽章是否可以使用來源的引數來呼叫,來解析簽章相容性,且允許額外的引數。例如,此程式碼僅在使用可選參數正確撰寫簽章時才會公開錯誤
ts
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 errorfn(x.diff);
第二個原因是使用者使用 TypeScript 的「嚴格 null 檢查」功能時。由於未指定參數在 JavaScript 中顯示為 undefined
,因此通常可以將明確的 undefined
傳遞給具有可選引數的函式。例如,此程式碼在嚴格 null 檢查下應為正常
ts
var x: Example;// When written with overloads, incorrectly an error because of passing 'undefined' to 'string'// When written with optionals, correctly OKx.diff("something", true ? undefined : "hour");
使用聯合類型
❌ 請勿撰寫僅在一個引數位置類型不同的重載
ts
/* WRONG */interface Moment {utcOffset(): number;utcOffset(b: number): Moment;utcOffset(b: string): Moment;}
✅ 請盡可能使用聯合類型
ts
/* OK */interface Moment {utcOffset(): number;utcOffset(b: number | string): Moment;}
請注意,我們在此未將 b
設為可選,因為簽章的回傳類型不同。
❔ 原因:這對於「傳遞」值給函式的人來說很重要
ts
function fn(x: string): Moment;function fn(x: number): Moment;function fn(x: number | string) {// When written with separate overloads, incorrectly an error// When written with union types, correctly OKreturn moment().utcOffset(x);}