通用类型
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 常见问题解答页面。
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 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;}
重载和回调
❌ 不要编写仅在回调参数个数上不同的单独重载
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 通过查看目标的任何签名是否可以用源的参数调用来解析签名兼容性,并且允许多余的参数。例如,此代码仅在使用可选参数正确编写签名时才会暴露错误
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 errorfn(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 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
设置为可选,因为签名的返回类型不同。
❔ 为什么: 这对于“传递”值到函数的人很重要
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 OKreturn moment().utcOffset(x);}