最佳實踐 (Do's and Don'ts)

通用型別

Number, String, Boolean, SymbolObject

不要使用型別 NumberStringBooleanSymbolObject。這些型別指的是非原始的包裝物件,它們在 JavaScript 程式碼中幾乎從未使用得當。

ts
/* WRONG */
function reverse(s: String): String;

使用型別 numberstringbooleansymbol

ts
/* OK */
function reverse(s: string): string;

對於 Object,請使用非原始型別 object於 TypeScript 2.2 引入)。

泛型

不要定義不使用其型別引數的泛型型別。詳情請參考 TypeScript FAQ 頁面

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 else
k.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;

編寫一個使用最大引數數量的單一過載。

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 透過檢查目標籤名是否可以使用源的引數來呼叫(並且允許額外的引數)來解析簽名相容性。例如,這段程式碼只有在正確使用可選引數編寫簽名時,才會暴露一個 bug。

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 error
fn(x.diff);

第二個原因是當使用者使用 TypeScript 的“嚴格空值檢查”功能時。因為未指定的引數在 JavaScript 中表現為 undefined,所以通常可以將顯式的 undefined 傳遞給帶有可選引數的函式。例如,這段程式碼在嚴格空值檢查下應該是沒問題的。

ts
var x: Example;
// When written with overloads, incorrectly an error because of passing 'undefined' to 'string'
// When written with optionals, correctly OK
x.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 OK
return moment().utcOffset(x);
}

TypeScript 文件是一個開源專案。歡迎傳送 Pull Request 幫助我們改進這些頁面 ❤

此頁面的貢獻者
MHMohamed Hegazy (55)
OTOrta Therox (15)
MZMicah Zoltu (3)
MFMartin Fischer (1)
JJongbin (1)
15+

最後更新:2026 年 3 月 27 日