注意事项
一般类型
Number、String、Boolean、Symbol 和 Object
❌ 不要 使用类型 Number、String、Boolean、Symbol 或 Object 这些类型指的是非原始类型的装箱对象,在 JavaScript 代码中几乎从未被恰当使用。
/* 错误 */
function reverse(s: String): String;✅ 要 使用类型 number、string、boolean 和 symbol。
/* 正确 */
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:
/* 错误 */
function fn(x: () => any) {
x();
}✅ 要 为那些返回值将被忽略的回调使用返回类型 void:
/* 正确 */
function fn(x: () => void) {
x();
}❔ 为什么: 使用 void 更安全,因为它可以防止你以未检查的方式意外使用 x 的返回值:
function fn(x: () => void) {
var k = x(); // 哎呀!本想做别的事情
k.doSomething(); // 错误,但如果返回类型是 'any' 则会是正确的
}回调中的可选参数
❌ 不要 在回调中使用可选参数,除非你确实需要:
/* 错误 */
interface Fetcher {
getObject(done: (data: unknown, elapsedTime?: number) => void): void;
}这有一个非常具体的含义:done 回调可能使用 1 个参数调用,也可能使用 2 个参数调用。 作者可能想表达的是回调可能不关心 elapsedTime 参数, 但没必要通过使参数可选来实现这一点—— 提供接受更少参数的回调总是合法的。
✅ 要 将回调参数写为非可选的:
/* 正确 */
interface Fetcher {
getObject(done: (data: unknown, elapsedTime: number) => void): void;
}重载与回调
❌ 不要 编写仅在回调元数上不同的独立重载:
/* 错误 */
declare function beforeAll(action: () => void, timeout?: number): void;
declare function beforeAll(
action: (done: DoneFn) => void,
timeout?: number
): void;✅ 要 使用最大元数编写单个重载:
/* 正确 */
declare function beforeAll(
action: (done: DoneFn) => void,
timeout?: number
): void;❔ 为什么: 回调忽略参数总是合法的,因此不需要较短的重载。 将较短的回调放在前面会允许传入类型错误的函数,因为它们匹配第一个重载。
函数重载
排序
❌ 不要 将更一般的重载放在更具体的重载之前:
/* 错误 */
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,怎么回事?✅ 要 将更具体的签名放在更一般的签名之后来排序重载:
/* 正确 */
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 在解析函数调用时会选择第一个匹配的重载。 当一个较早的重载比后面的更“一般”时,后面的实际上被隐藏了,无法被调用。
使用可选参数
❌ 不要 编写仅在尾随参数上不同的多个重载:
/* 错误 */
interface Example {
diff(one: string): number;
diff(one: string, two: string): number;
diff(one: string, two: string, three: boolean): number;
}✅ 要 尽可能使用可选参数:
/* 正确 */
interface Example {
diff(one: string, two?: string, three?: boolean): number;
}注意,这种合并只应在所有重载具有相同返回类型时进行。
❔ 为什么: 这很重要,原因有二。
TypeScript 通过查看目标类型的任何签名是否可以用源类型的参数调用来解析签名兼容性, 并且允许额外的参数。 例如,这段代码仅在签名使用可选参数正确编写时才暴露出一个 bug:
function fn(x: (a: string, b: number, c: number) => void) {}
var x: Example;
// 使用重载编写时,正确 – 使用第一个重载
// 使用可选参数编写时,正确地报错
fn(x.diff);第二个原因是当使用者使用 TypeScript 的“严格空值检查”功能时。 因为未指定的参数在 JavaScript 中显示为 undefined,通常可以向具有可选参数的函数传递显式的 undefined。 例如,这段代码在严格空值检查下应该没问题:
var x: Example;
// 使用重载编写时,因将 'undefined' 传递给 'string' 而错误地报错
// 使用可选参数编写时,正确地通过
x.diff("something", true ? undefined : "hour");使用联合类型
❌ 不要 编写仅在单个参数位置上类型不同的重载:
/* 错误 */
interface Moment {
utcOffset(): number;
utcOffset(b: number): Moment;
utcOffset(b: string): Moment;
}✅ 要 尽可能使用联合类型:
/* 正确 */
interface Moment {
utcOffset(): number;
utcOffset(b: number | string): Moment;
}注意我们这里没有将 b 设为可选,因为签名的返回类型不同。
❔ 为什么: 这对于那些“传递”一个值给你的函数的人很重要:
function fn(x: string): Moment;
function fn(x: number): Moment;
function fn(x: number | string) {
// 使用独立重载编写时,错误地报错
// 使用联合类型编写时,正确地通过
return moment().utcOffset(x);
}