泛型类型
🌐 General Types
Number、String、Boolean、Symbol 和 Object
🌐 Number, String, Boolean, Symbol and Object
❌ 不要 使用类型 Number、String、Boolean、Symbol 或 Object
这些类型指的是非原始封装对象,在 JavaScript 代码中几乎从未被适当使用。
ts/* WRONG */function reverse(s: String): String;
✅ 使用 类型 number、string、boolean 和 symbol。
ts/* OK */function reverse(s: string): string;
不要使用 Object,而应使用非原始类型 object(在 TypeScript 2.2 中新增)。
🌐 Instead of Object, use the non-primitive object type (added in TypeScript 2.2).
泛型
🌐 Generics
❌ 不要 创建不使用其类型参数的泛型类型。
更多详情见 TypeScript 常见问题页面。
any
❌ 不要 使用 any 作为类型,除非你正在将一个 JavaScript 项目迁移到 TypeScript。编译器实际上会将 any 视为“请关闭这个东西的类型检查”。这类似于在变量的每次使用中添加 @ts-ignore 注释。当你刚将 JavaScript 项目迁移到 TypeScript 时,这可能很有用,因为你可以将尚未迁移的内容的类型设置为 any,但在完整的 TypeScript 项目中,你会禁用程序中任何使用它的部分的类型检查。
在你不知道想接受哪种类型,或者你想接受任何类型因为你会直接传递而不进行处理的情况下,你可以使用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:
ts/* WRONG */function fn(x: () => any) {x();}
✅ 使用返回类型 void 对于其值将被忽略的回调函数:
ts/* OK */function fn(x: () => void) {x();}
❔ 原因: 使用 void 更安全,因为它可以防止你不小心以未检查的方式使用 x 的返回值:
tsfunction 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'}
回调中的可选参数
🌐 Optional Parameters in Callbacks
❌ 不要 在回调中使用可选参数,除非你真的想这样做:
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.
✅ 应该 将回调参数写为非可选项:
ts/* OK */interface Fetcher {getObject(done: (data: unknown, elapsedTime: number) => void): void;}
重载和回调
🌐 Overloads and Callbacks
❌ 不要编写仅在回调参数数量上不同的单独重载:
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;
❔ 原因: 回调函数忽略参数总是合法的,因此不需要较短的重载。先提供较短的回调函数会导致类型错误的函数被传入,因为它们匹配第一个重载。
函数重载
🌐 Function Overloads
排序
🌐 Ordering
❌ 不要将更通用的重载放在更具体的重载之前:
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 在解析函数调用时会选择_第一个匹配的重载_。当较早的重载“更通用”时,后面的重载实际上被隐藏,无法调用。
使用可选参数
🌐 Use Optional Parameters
❌ 不要编写仅在尾部参数不同的多个重载函数:
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;}
请注意,只有当所有重载都具有相同的返回类型时,才会发生这种折叠。
🌐 Note that this collapsing should only occur when all overloads have the same return type.
❔ 原因: 这有两个原因很重要。
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:
tsfunction 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 的“严格空值检查”功能时。
因为未指定的参数在 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:
tsvar 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");
使用联合类型
🌐 Use Union Types
❌ 不要编写仅在一个参数位置上类型不同的重载:
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 设为可选,因为签名的返回类型不同。
🌐 Note that we didn’t make b optional here because the return types of the signatures differ.
❔ 原因: 这对于那些将一个值“传递”给你的函数的人来说很重要:
tsfunction 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);}