TypeScript 编码规范
TypeScript
是微软开发的一款开源编程语言,它是 JavaScript
的超集,因此其编码规范和配套 Lint
工具也与JavaScript编码规范一脉相承。
编码风格
-
【强制】重载的函数必须写在一起 @typescript-eslint/adjacent-overload-signatures
自然相关的项组合在一起将提高代码可读性和组织性。
// bad
declare namespace Foo {
export function foo(s: string): void;
export function foo(n: number): void;
export function bar(): void;
export function foo(sn: string | number): void;
}
// good
declare namespace Foo {
export function foo(s: string): void;
export function foo(n: number): void;
export function foo(sn: string | number): void;
export function bar(): void;
}// bad
type Foo = {
foo(s: string): void;
foo(n: number): void;
bar(): void;
foo(sn: string | number): void;
};
interface Foo {
foo(s: string): void;
foo(n: number): void;
bar(): void;
foo(sn: string | number): void;
}
class Foo {
foo(s: string): void;
foo(n: number): void;
bar(): void {}
foo(sn: string | number): void {}
}
// good
type Foo = {
foo(s: string): void;
foo(n: number): void;
foo(sn: string | number): void;
bar(): void;
};
interface Foo {
foo(s: string): void;
foo(n: number): void;
foo(sn: string | number): void;
bar(): void;
}
class Foo {
foo(s: string): void;
foo(n: number): void;
foo(sn: string | number): void {}
bar(): void {}
}// bad
export function foo(s: string): void;
export function foo(n: number): void;
export function bar(): void;
export function foo(sn: string | number): void;
// good
export function bar(): void;
export function foo(s: string): void;
export function foo(n: number): void;
export function foo(sn: string | number): void; -
【推荐】简单数组类型的定义使用
T[]
,复杂类型使用Array<T>
@typescript-eslint/array-type对数组类型的定义使用相同的规范将帮助开发者更快地理解和阅读类型。
简单类型(数字、字符串、布尔等)请使用
T[]
或readonly T[]
,其他复杂类型(联合、交叉、对象、函数等)请使用Array<T>
或ReadonlyArray<T>
// bad
const a: (string | number)[] = ['a', 1];
const b: { prop: string }[] = [{ prop: 'a' }];
const c: (() => void)[] = [() => {}];
const d: Array<MyType> = ['a', 'b'];
const e: Array<string> = ['a', 'b'];
const f: ReadonlyArray<string> = ['a', 'b'];
// good
const a: Array<string | number> = ['a', 1];
const b: Array<{ prop: string }> = [{ prop: 'a' }];
const c: Array<() => void> = [() => {}];
const d: MyType[] = ['a', 'b'];
const e: string[] = ['a', 'b'];
const f: readonly string[] = ['a', 'b']; -
【推荐】使用
TypeScript
注释指令时需跟随描述说明 @typescript-eslint/ban-ts-commentTS 提供了一些指令注释,可用于忽略
TypeScript
编译器在编译阶段的错误,如下:// @ts-expect-error
// @ts-ignore
// @ts-nocheck
// @ts-check我们允许在代码中使用指令注释,但需要跟随一定长度的描述说明。
// bad
// @ts-expect-error
console.log('my code');
// @ts-ignore
console.log('my code');
// good
// @ts-expect-error: Unreachable code here
console.log('my code');
// @ts-ignore: It's ok to ignore this compile error
console.log('my code'); -
【强制】禁止使用
// tslint:<rule-flag>
等tslint
注释 @typescript-eslint/ban-tslint-commenttslint
已经被废弃,对应的指令注释也不应再出现。// bad
/* tslint:disable */
/* tslint:enable */
/* tslint:disable:rule1 rule2 rule3... */
/* tslint:enable:rule1 rule2 rule3... */
// tslint:disable-next-line
someCode(); // tslint:disable-line
// tslint:disable-next-line:rule1 rule2 rule3... -
【推荐】如果类的属性是一个字面量,则推荐使用只读属性
readonly
而不是getter
@typescript-eslint/class-literal-property-style类上所有返回「字面量」的
getter
方法,都推荐使用readonly
修饰符来代替,字面量包含字符串、模板字符串、数字、bigint
、正则和null。
说明:在一些特殊场景,编写代码的最终用户是
JavaScript
开发者时,可以使用getter
来保证字段无法被重新定义和覆盖,因为readonly
修饰符只作用于TypeScript
编译阶段。// bad
class Mx {
public static get myField1() {
return 1;
}
private get ['myField2']() {
return 'hello world';
}
}
// good
class Mx {
public readonly myField1 = 1;
public readonly myField2 = [1, 2, 3]; // 非字面量
private readonly ['myField3'] = 'hello world';
public get myField4() {
return `hello from ${window.location.href}`;
}
} -
【强制】类型断言必须使用
as Type
@typescript-eslint/consistent-type-assertions类型断言(
type assertiions
)也可称作类型转换(type casting
),本质上是对TypeScript
类型系统的人为干预:-
强制对类型的断言统一使用
as Type
风格而非<Type>
,后者容易与JSX
产生混淆。 -
对象字面量禁止类型断言,断言成
any
除外,对象字面量应该直接声明。
TypeScript 3.4
中引入的const
断言在本规则中不受约束,let x = 'hello' as const;
和let x = <const>'hello';
都是允许的。// bad
const foo = <string>'bar';
// good
const foo = 'bar' as string;// bad
const x = { ... } as T;
const y = { ... } as object;
// good
const x: T = { ... };
const y = { ... } as any;
const z = { ... } as unknown; -
-
【推荐】优先使用
interface
定义类型 @typescript-eslint/consistent-type-definitionsinterface 支持 extend/implement/union 等等类型能力,同时也可以用于描述普通对象。
// bad
type T = { x: number };
// good
type T = string;
type Foo = string | {};
interface T {
x: number;
} -
【推荐】设置类成员的可访问性 @typescript-eslint/explicit-member-accessibility
将非公开成员的可访问性设置为「私有」,可以增强代码可理解性,同时也能避免一些非法调用,公开的成员可省略
public
修饰符。// bad
class Foo {
static foo = 'foo';
static getFoo() {
return Foo.foo;
}
constructor() {}
bar = 'bar';
getBar() {}
get baz() {
return 'baz';
}
set baz(value) {
console.log(value);
}
}
// good
class Foo {
private static foo = 'foo';
public static getFoo() {
return Foo.foo;
}
public constructor() {}
protected bar = 'bar';
public getBar() {}
public get baz() {
return 'baz';
}
public set baz(value) {
console.log(value);
}
} -
【强制】
interface/type
类型中使用一致的成员分隔符;
@typescript-eslint/member-delimiter-style// bad: comma style(JSON style)
interface Foo {
name: string;
greet(): void;
}
type Bar = {
name: string;
greet(): void;
};
// bad: line break style
interface Foo {
name: string;
greet(): void;
}
type Bar = {
name: string;
greet(): void;
};
// good
interface Foo {
name: string;
greet(): void;
}
type Bar = {
name: string;
greet(): void;
}; -
【推荐】类的成员应按照固定的先后顺序排列 @typescript-eslint/member-ordering
- 类的静态方法 / 属性(
static
)优先于实例的方法 / 属性(instance
) - 属性(
field
)优先于构造函数(constructor
),优先于方法(method
) - 公开的成员(
public
)优先于受保护的成员(protected
),优先于私有的成员(private
)
// good
class Foo {
public static foo1 = 'foo1';
protected static foo2 = 'foo2';
private static foo3 = 'foo3';
public static getFoo1() {}
protected static getFoo2() {}
private static getFoo3() {
return Foo.foo3;
}
public bar1 = 'bar1';
protected bar2 = 'bar2';
private bar3 = 'bar3';
public constructor() {
console.log(Foo.getFoo3());
console.log(this.getBar3());
}
public getBar1() {}
protected getBar2() {}
private getBar3() {
return this.bar3;
}
} - 类的静态方法 / 属性(
-
【推荐】接口中的方法使用属性的方式定义 @typescript-eslint/method-signature-style
使用属性去定义接口中的方法,可以获得更严格的检查。
// bad
interface T1 {
func(arg: string): number;
}
type T2 = {
func(arg: boolean): void;
};
interface T3 {
func(arg: number): void;
func(arg: string): void;
func(arg: boolean): void;
}
// good
interface T1 {
func: (arg: string) => number;
}
type T2 = {
func: (arg: boolean) => void;
};
// 属性方法实现重载
interface T3 {
func: ((arg: number) => void) & ((arg: string) => void) & ((arg: boolean) => void);
} -
【推荐】禁止使用容易混淆的非空断言 @typescript-eslint/no-confusing-non-null-assertion
在相等比较运算符(
==
或===
)前使用非空断言(!
)很容易和不等运算符(!=
或!==
)混淆,不建议使用。interface Foo {
bar?: string;
num?: number;
}
// bad
const foo: Foo = getFoo();
const isEqualsBar = foo.bar! == 'hello';
const isEqualsNum = 1 + foo.num! == 2;
// good
const foo: Foo = getFoo();
const isEqualsBar = foo.bar == 'hello';
const isEqualsNum = 1 + foo.num! == 2; -
【推荐】避免定义空的接口类型 @typescript-eslint/no-empty-interface
空的接口类型等效于空对象,若它只继承另一个接口类型,那么该类型与被继承的类型等效。在代码中应减少定义无意义的接口类型。
// bad
// an empty interface
interface Foo {}
// an interface with only one supertype (Bar === Foo)
interface Bar extends Foo {}
// an interface with an empty list of supertypes
interface Baz {}
// good
// an interface with any number of members
interface Foo {
name: string;
}
// same as above
interface Bar {
age: number;
}
// an interface with more than one supertype
// in this case the interface can be used as a replacement of a union type.
interface Baz extends Foo, Bar {} -
【推荐】初始化为
number/string/boolean
的变量或参数应避免显式的类型声明 @typescript-eslint/no-inferrable-types对于容易类型推倒出的变量、参数,再次的显式声明类型会带来代码冗余。
// bad
const foo: number = 1;
const bar: string = '';
class Foo {
prop: number = 5;
}
function fn(a: number = 5, b: boolean = true) {}
// good
const foo = 1;
const bar = '';
class Foo {
prop = 5;
}
function fn(a = 5, b = true) {} -
【强制】禁止无意义的
void
类型 @typescript-eslint/no-invalid-void-type禁止在返回类型或泛型类型参数之外使用
void
类型,而且在返回类型中不应再与其他类型做联合或交叉。void
类型代表「无」或函数「不返回任何值」,隐式未定义类型代表函数返回「未定义的值 undefined」,所以void
类型无法与除了never
外的其他类型做联合、交叉。// bad
type PossibleValues = string | number | void;
type MorePossibleValues = string | ((number & any) | (string | void));
function logSomething(thing: void) {}
function printArg<T = void>(arg: T) {}
logAndReturn<void>(undefined);
interface Interface {
lambda: () => void;
prop: void;
}
class MyClass {
private readonly propName: void;
}
// good
type NoOp = () => void;
function noop(): void {}
let trulyUndefined = void 0;
async function promiseMeSomething(): Promise<void> {}
type stillVoid = void | never; -
【强制】禁止使用
namespace
来定义命名空间 @typescript-eslint/no-namespace自定义
TypeScript
模块(module
)和命名空间(namespace
)已经不再推荐使用,首选ES2015
的模块语法来导入导出。此规则仍然允许定义外部的模块或命名空间。// bad
module foo {}
namespace foo {}
// good
declare module 'foo' {}
declare module foo {}
declare namespace foo {}
declare global {
namespace foo {}
}
declare module foo {
namespace foo {}
} -
【强制】禁止在
optional chaining
之后使用non-null
断言 @typescript-eslint/no-non-null-asserted-optional-chainoptional chaining
被设计为返回undefined
,在之后使用非空断言是错误的,会引入严重的类型安全问题。// bad
foo?.bar!;
foo?.bar!.baz;
foo?.bar()!;
foo?.bar!();
foo?.bar!().baz;
// good
foo?.bar;
(foo?.bar).baz;
foo?.bar();
foo?.bar();
foo?.bar().baz;
// 如果您使用的是 TS3.9 或更高版本,则以下代码也是正确的
foo?.bar!.baz;
foo?.bar!();
foo?.bar!().baz; -
【推荐】使用
ES2015 import
语法引入模块 @typescript-eslint/no-require-imports// bad
const fs = require('fs');
// good
import * as fs from 'fs'; -
【推荐】不建议将
this
赋值给其他变量 @typescript-eslint/no-this-alias通过变量赋值为
this
的方式来管理函数作用域不是我们推荐的最佳实践,应使用箭头函数保留函数作用域。此规则中允许对 this 的解构赋值。// bad
function foo() {
const self = this;
setTimeout(function () {
self.doWork();
});
}
// good
function foo() {
setTimeout(() => {
this.doWork();
});
} -
【推荐】当变量的值与类型声明相等时,优先使用
as const
@typescript-eslint/prefer-as-const// bad
let bar: 2 = 2;
let foo = <'bar'>'bar';
let foo = { bar: 'baz' as 'baz' };
// good
let foo = 'bar';
let foo = 'bar' as const;
let foo: 'bar' = 'bar' as const;
let bar = 'bar' as string;
let foo = <string>'bar';
let foo = { bar: 'baz' }; -
【强制】禁止使用
module
来定义命名空间 @typescript-eslint/prefer-namespace-keywordmodule
已经成为 JS 语言的关键字,应避免TypeScript
模块与ES2015
模块混淆。declare module
不做限制。// bad
module Foo {}
// good
declare module Foo {}
declare namespace Foo {} -
【强制】字符串字面量使用单引号包裹 @typescript-eslint/quotes
// bad
const foo = 'bar';
// good
const foo = 'bar'; -
【推荐】 加号
+
连接的两侧同为数字或同为字符串 @typescript-eslint/restrict-plus-operands数字与字符串的连接往往会导致一些预期外的问题。
// bad
var foo = '5.5' + 5;
var foo = 1n + 1;
// good
var foo = parseInt('5.5', 10) + 10;
var foo = 1n + 1n; -
【强制】禁止使用三斜杠语法
///
导入文件 @typescript-eslint/triple-slash-reference三斜杠语法已经被废弃,声明文件(d.ts)以外禁止使用。
// bad
/// <reference path="./my-module" />
// good
import myModule from './my-module'; -
【强制】类型声明时应正确添加空格间距 @typescript-eslint/type-annotation-spacing
TypeScript
类型声明周围添加合适的间距可以有效的提升代码可读性,我们约定:- 冒号前无空格,冒号后保留一个空格
- 箭头前后都保留一个空格
// bad
let foo: string = 'bar';
let foo: string = 'bar';
let foo: string = 'bar';
function foo(): string {}
function foo(): string {}
function foo(): string {}
class Foo {
name: string;
}
class Foo {
name: string;
}
class Foo {
name: string;
}
type Foo = () => {};
// good
let foo: string = 'bar';
function foo(): string {}
class Foo {
name: string;
}
type Foo = () => {}; -
【强制】
interface
和type
定义时必须声明成员的类型 @typescript-eslint/typedef// bad
type Members = {
member;
otherMember;
};
// good
type Members = {
member: boolean;
otherMember: string;
}; -
【推荐】定义函数时,优先使用参数的联合类型而不是函数的类型重载 @typescript-eslint/unified-signatures
// bad
function f(x: number): void;
function f(x: string): void;
f(): void;
f(...x: number[]): void;
// good
function f(x: number | string): void;
function f(x?: ...number[]): void;