通用类型
Number, String, Boolean, Symbol 和 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 引入)。
泛型
❌ 不要定义不使用其类型参数的泛型类型。详情请参考 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 的返回值。
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'}
回调函数中的可选参数
❌ 不要在回调函数中使用可选参数,除非你确实有此意图。
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。
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 传递给带有可选参数的函数。例如,这段代码在严格空值检查下应该是没问题的。
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");
使用联合类型
❌ 不要编写仅在一个参数位置上类型不同的重载。
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 设为可选,因为各签名的返回类型不同。
❔ 为什么: 这对于那些将值“传递”给你的函数的人来说很重要。
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);}