最佳实践 (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 日