该做什么和不该做什么

泛型类型

Number、String、Boolean、Symbol 和 Object

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

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

✅ 请使用 numberstringbooleansymbol 类型。

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

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

泛型

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

any

❌ 不要使用 any 作为类型,除非你正在将 JavaScript 项目迁移到 TypeScript。编译器有效地将 any 视为 "please turn off type checking for this thing"。它类似于在变量的每个用法周围放置 @ts-ignore 注释。当您第一次将 JavaScript 项目迁移到 TypeScript 时,这可能非常有用,因为您可以将尚未迁移的内容的类型设置为 any,但在完整的 TypeScript 项目中,您将禁用对程序的任何部分的类型检查 用它。

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

回调类型

回调的返回类型

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

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

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

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

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

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

回调中的可选参数

❌ 不要在回调中使用可选参数,除非你真的是这个意思:

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

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

✅ 不要把回调参数写成非可选的:

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

重载和回调

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

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

✅ 务必使用最大元数编写单个重载:

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

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

函数重载

排序

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

/* 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?

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

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

使用可选参数

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

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

✅ 尽可能使用可选参数:

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

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

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

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

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 的 "strict null checking" 特性时。因为未指定的参数在 JavaScript 中显示为 undefined,所以通常可以将显式 undefined 传递给具有可选参数的函数。例如,这段代码在严格的空值下应该没问题:

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

使用联合类型

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

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

✅ 尽可能使用联合类型:

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

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

为什么:这对于 "passing through" 对您的职能有价值的人很重要:

function fn(x: string): void;
function fn(x: number): void;
function fn(x: number | string) {
  // When written with separate overloads, incorrectly an error
  // When written with union types, correctly OK
  return moment().utcOffset(x);
}