该做什么和不该做什么

泛型类型

¥General Types

NumberStringBooleanSymbolObject

¥Number, String, Boolean, Symbol and Object

❌ 永远不要使用类型 NumberStringBooleanSymbolObject 这些类型指的是非原始装箱对象,它们几乎从未在 JavaScript 代码中正确使用过。

¥❌ Don’t ever use the types Number, String, Boolean, Symbol, or Object These types refer to non-primitive boxed objects that are almost never used appropriately in JavaScript code.

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

✅ 请使用 numberstringbooleansymbol 类型。

¥✅ Do use the types number, string, boolean, and symbol.

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

使用非原始的 object 类型 (在 TypeScript 2.2 中添加) 而不是 Object

¥Instead of Object, use the non-primitive object type (added in TypeScript 2.2).

泛型

¥Generics

❌ 永远不要有不使用其类型参数的泛型类型。在 TypeScript 常见问题页面 中查看更多详细信息。

¥❌ Don’t ever have a generic type which doesn’t use its type parameter. See more details in TypeScript FAQ page.

any

❌ 除非你正在将 JavaScript 项目迁移到 TypeScript,否则请勿使用 any 作为类型。编译器有效地将 any 视为 “请关闭这个东西的类型检查”。它类似于在变量的每个用法周围放置 @ts-ignore 注释。当你第一次将 JavaScript 项目迁移到 TypeScript 时,这可能非常有用,因为你可以将尚未迁移的内容的类型设置为 any,但在完整的 TypeScript 项目中,你将禁用对程序的任何部分的类型检查 用它。

¥❌ Don’t use any as a type unless you are in the process of migrating a JavaScript project to TypeScript. The compiler effectively treats any as “please turn off type checking for this thing”. It is similar to putting an @ts-ignore comment around every usage of the variable. This can be very helpful when you are first migrating a JavaScript project to TypeScript as you can set the type for stuff you haven’t migrated yet as any, but in a full TypeScript project you are disabling type checking for any parts of your program that use it.

如果你不知道你想要接受什么类型,或者当你想接受任何东西因为你会盲目地传递它而不与之交互,你可以使用 unknown

¥In cases where you don’t know what type you want to accept, or when you want to accept anything because you will be blindly passing it through without interacting with it, you can use unknown.

回调类型

¥Callback Types

回调的返回类型

¥Return Types of Callbacks

❌ 不要将返回类型 any 用于其值将被忽略的回调:

¥❌ Don’t use the return type any for callbacks whose value will be ignored:

ts
/* WRONG */
function fn(x: () => any) {
x();
}

✅ 对于其值将被忽略的回调,请使用返回类型 void

¥✅ Do use the return type void for callbacks whose value will be ignored:

ts
/* OK */
function fn(x: () => void) {
x();
}

❔ 为什么:使用 void 更安全,因为它可以防止你意外地以未经检查的方式使用 x 的返回值:

¥❔ Why: Using void is safer because it prevents you from accidentally using the return value of x in an unchecked way:

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'
}

回调中的可选参数

¥Optional Parameters in Callbacks

❌ 不要在回调中使用可选参数,除非你真的这么想:

¥❌ Don’t use optional parameters in callbacks unless you really mean it:

ts
/* WRONG */
interface Fetcher {
getObject(done: (data: unknown, elapsedTime?: number) => void): void;
}

这有一个非常具体的含义:done 回调可以用 1 个参数调用,也可以用 2 个参数调用。作者可能是想说回调可能不关心 elapsedTime 参数,但没有必要使参数可选来实现这一点 - 提供接受较少参数的回调总是合法的。

¥This has a very specific meaning: the done callback might be invoked with 1 argument or might be invoked with 2 arguments. The author probably intended to say that the callback might not care about the elapsedTime parameter, but there’s no need to make the parameter optional to accomplish this — it’s always legal to provide a callback that accepts fewer arguments.

✅ 请将回调参数写为非可选:

¥✅ Do write callback parameters as non-optional:

ts
/* OK */
interface Fetcher {
getObject(done: (data: unknown, elapsedTime: number) => void): void;
}

重载和回调

¥Overloads and Callbacks

❌ 不要编写仅在回调数量上不同的单独重载:

¥❌ Don’t write separate overloads that differ only on callback arity:

ts
/* WRONG */
declare function beforeAll(action: () => void, timeout?: number): void;
declare function beforeAll(
action: (done: DoneFn) => void,
timeout?: number
): void;

✅ 请使用最大数量编写单个重载:

¥✅ Do write a single overload using the maximum arity:

ts
/* OK */
declare function beforeAll(
action: (done: DoneFn) => void,
timeout?: number
): void;

❔ 为什么:忽略参数的回调始终是合法的,因此不需要更短的重载。首先提供较短的回调允许传入错误类型的函数,因为它们与第一个重载匹配。

¥❔ Why: It’s always legal for a callback to disregard a parameter, so there’s no need for the shorter overload. Providing a shorter callback first allows incorrectly-typed functions to be passed in because they match the first overload.

函数重载

¥Function Overloads

排序

¥Ordering

❌ 不要将更一般的重载放在更具体的重载之前:

¥❌ Don’t put more general overloads before more specific overloads:

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?

✅ 通过将更通用的签名放在更具体的签名后面来对重载进行排序:

¥✅ Do sort overloads by putting the more general signatures after more specific signatures:

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 在解析函数调用时选择第一个匹配的重载。当较早的重载比较晚的重载为 “更一般” 时,较晚的重载实际上被隐藏并且无法调用。

¥❔ Why: TypeScript chooses the first matching overload when resolving function calls. When an earlier overload is “more general” than a later one, the later one is effectively hidden and cannot be called.

使用可选参数

¥Use Optional Parameters

❌ 不要编写仅尾随参数不同的多个重载:

¥❌ Don’t write several overloads that differ only in trailing parameters:

ts
/* WRONG */
interface Example {
diff(one: string): number;
diff(one: string, two: string): number;
diff(one: string, two: string, three: boolean): number;
}

✅ 尽可能使用可选参数:

¥✅ Do use optional parameters whenever possible:

ts
/* OK */
interface Example {
diff(one: string, two?: string, three?: boolean): number;
}

请注意,只有当所有重载都具有相同的返回类型时,才会发生这种折叠。

¥Note that this collapsing should only occur when all overloads have the same return type.

❔ 为什么:这很重要,原因有二。

¥❔ Why: This is important for two reasons.

TypeScript 通过查看是否可以使用源的参数调用目标的任何签名来解决签名兼容性问题,并且允许使用无关的参数。例如,此代码仅在使用可选参数正确编写签名时才会暴露错误:

¥TypeScript resolves signature compatibility by seeing if any signature of the target can be invoked with the arguments of the source, and extraneous arguments are allowed. This code, for example, exposes a bug only when the signature is correctly written using optional parameters:

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 传递给具有可选参数的函数。例如,这段代码在严格的空值下应该没问题:

¥The second reason is when a consumer uses the “strict null checking” feature of TypeScript. Because unspecified parameters appear as undefined in JavaScript, it’s usually fine to pass an explicit undefined to a function with optional arguments. This code, for example, should be OK under strict nulls:

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");

使用联合类型

¥Use Union Types

❌ 不要仅在一个参数位置编写因类型而异的重载:

¥❌ Don’t write overloads that differ by type in only one argument position:

ts
/* WRONG */
interface Moment {
utcOffset(): number;
utcOffset(b: number): Moment;
utcOffset(b: string): Moment;
}

✅ 尽可能使用联合类型:

¥✅ Do use union types whenever possible:

ts
/* OK */
interface Moment {
utcOffset(): number;
utcOffset(b: number | string): Moment;
}

请注意,我们在这里没有将 b 设为可选,因为签名的返回类型不同。

¥Note that we didn’t make b optional here because the return types of the signatures differ.

❔ 为什么:这对于 “传递” 对你的函数有价值的人很重要:

¥❔ Why: This is important for people who are “passing through” a value to your function:

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);
}