此推荐 config 适用于 typescript v2.7.x 以上。建议使用
tsc --init
自动生成配置文件,会有相应的配置信息提示。
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"checkJs": true,
"jsx": "react",
"sourceMap": true,
"importHelpers": true,
"strict": true,
"alwaysStrict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"moduleResolution": "node",
"esModuleInterop": true
}
}
其中需要注意以下几点:
-
target 若无特殊情况默认
ES5
,其余情况下自行查询 node 或 browser 的兼容性后可配置为ES2015+
。 -
module 默认为
commonjs
。-
node 环境下永远为
commonjs
。 -
若要配合 antd 使用,需设置为
es2015
。 -
使用 dynamic import (前端代码切割) 时需配置为
esnext
。 -
前端打包一个库时编译为
umd
。
-
-
jsx 使用 react 时配置为
react
。 -
默认开启严格模式及几个严格的检查。其中可酌情关闭的有:
-
strictNullChecks
: 适用于将 undefined 和 null 赋值给其他类型。 -
strictPropertyInitialization
: 检查类成员是否初始化。 -
noUnusedParameters
: 是否有未使用的参数。
-
-
需要使用装饰器时,开启如下两个选项。
{ "experimentalDecorators": true, "emitDecoratorMetadata": true, }
-
2.1 文件夹一律使用
小写
命名。使用"-"
来分隔描述性单词。例如react-dom
。 -
2.2 推荐根据业务逻辑来划分文件夹。在保证目录结构清晰的情况下应尽量减少嵌套层次。
// bad ├── controllers │ ├── user.controller.ts │ └── xxx.controller.ts ├─── models │ ├── user.controller.ts │ └── xxx.controller.ts └─── test ├── user.spec.ts └── xxx.spec.ts // good ├── user │ ├── user.controller.ts │ ├── user.model.ts │ └── user.spec.ts └─── test ├── xxx.controller.ts ├── xxx.model.ts └── xxx.spec.ts
-
2.3 文件名使用
小写
命名。使用"-"
来分隔描述性单词。使用"."
来分隔类型。遵循feature.type.ts
的模式。例如user.controller.ts
。 -
2.4 单元测试文件添加
.spec
后缀,端到端测试文件添加.e2e-spec
后缀。 -
2.5
export name
应尽量与filename
保持一致。使用命名导出使而不是默认导出。(使用 react 的同学注意)。 -
2.6 其中可在模块文件夹(即上文中的 user 文件夹)下使用
index.ts
重新导出其他模块需要 import 的内容。Why?重新导出可以简化其他模块导入时的路径意为。
Why?重新导出意为
public api
,即不在该文件中导出的内容理论上作为模块的private api
不建议导入。 -
2.7 所有文件应遵循单一功能原则,一个文件一个类,考虑每个文件的代码限制在
400
行以内。Why?这有助于使代码更简洁,更容易阅读、维护和测试。
-
3.1 命名使用英文单词,不允许使用拼音,要见名知意,不常见的单词需添加中文注释。
-
3.2 当表示数组列表等可数名词要加
s
后缀,js 里由于没有类型,不知道是否是数组,推荐不可数名词也加上s
以示区别,ts 在有类型声明的情况下可以不用加。 -
3.3 函数方法名中动词使用一般现在时,
getXXX(),fetchData()
。 -
3.4 表生命周期的方法可以加时态,例如 react 式的
didMount
, vue 式的mounted
。 -
3.5 布尔值加上
is
前缀,isOK
, 表状态时加上时态,isLoading,isLoaded
。 -
3.6 原则上任何命名不允许缩写。
-
3.7 单个单词原则上坚决不允许缩写,尤其是用在变量声明时。例如
password => pwd
,但允许像obj
这种缩写,在函数参数名中,一些常见的可以使用缩写,例如information => info, parameters => params
。 -
3.8 拼接词长度超过 20 个可以使用缩写
-
缩写时采用常见的缩写方式(以能猜出单词意思或音译出为佳)例如:
button => btn
。 -
其余缩写方式以缩写单词前面字母为主,例如
parameters => params
。 -
如果单词实在过长或单词过多,采用缩写首字母方式, 例如
Hypertext Markup Language => HTML
,表明变量或方法的主要单词尽量不要缩写。 -
缩写词必须添加完整的单词注释,不常见的词要添加中文注释。
-
-
3.9 若无特殊说明,默认使用
camelCase
,保留前置下划线,但不是用于表达私有变量,多用于不可变数据重新赋值,考虑添加$
来表示一类特殊的值,例如 RxJS 中的 Observable。
-
4.1 在文件末尾保留一个空行。VSCODE 可配置
"files.insertFinalNewline": true
。 -
4.2 不允许有连续多行空行。
-
4.3 块语句不以空行结束。
-
4.4 块语句之后添加一个空行。
-
4.5 考虑
return
语句前添加一个空行。 -
4.6 禁止空的代码块。允许
function noop() {}
工具函数。 -
4.7 禁止行尾的空格。VSCODE 可配置
·"files.trimTrailingWhitespace": true
。 -
4.8 禁止水平对齐。
-
4.9 使用
两个空格
缩进。不要混用空格和制表符缩进。 -
4.10 以下情况前后各保留一个空格。
-
算术运算符(
+, -, *, /, %, **
) -
关系运算符(
in, instanceof, <, >, <=, >=
) -
相等操作符(
==, !=, ===, !==
) -
逻辑运算符(
&&, ||
) -
三元运算符(
?, :
) -
赋值运算符(
=, +=, -=, *=, /=, %=, **=
)
-
-
4.11 关键字之后保留一个空格。(
function, class, const, if, for ...
) -
4.12 冒号,逗号,分号之后保留一个空格,他们之前禁止保留空格。
-
4.13 圆括号,方括号之间不需要空格,花括号之间需要空格。
-
4.14 对象的计算属性不需要空格。
-
4.15 函数名和圆括号之间不需要空格。
-
4.16 左花括号之前需要一个空格。
-
5.1 需要分号,禁止多余的分号。
-
5.2 前端使用单引号,node 使用双引号,jsx 中使用双引号。
-
5.4 不要使用行首逗号。
// bad const story = [ once , upon , aTime ]; // good const story = [ once, upon, aTime, ]; // bad const hero = { firstName: 'Ada' , lastName: 'Lovelace' , birthYear: 1815 , superPower: 'computers' }; // good const hero = { firstName: 'Ada', lastName: 'Lovelace', birthYear: 1815, superPower: 'computers', };
-
5.5 考虑增加结尾的逗号。
Why? 这会让 git diffs 更干净。
// bad - git diff without trailing comma const hero = { firstName: 'Florence', - lastName: 'Nightingale' + lastName: 'Nightingale', + inventorOf: ['coxcomb graph', 'modern nursing'] } // good - git diff with trailing comma const hero = { firstName: 'Florence', lastName: 'Nightingale', + inventorOf: ['coxcomb chart', 'modern nursing'], } // bad const hero = { firstName: 'Dana', lastName: 'Scully' }; const heroes = [ 'Batman', 'Superman' ]; // good const hero = { firstName: 'Dana', lastName: 'Scully', }; const heroes = [ 'Batman', 'Superman', ];
-
6.1 使用
let
声明变量,const
声明常量。不要使用var
声明任何变量或常量。 -
6.2 变量使用
camelCase
命名,常量可使用camelCase, UPPER_CASE, PascalCase
命名。let a = 1; // 全局的常量使用 UPPER_CASE const API_URL = 'http://www.gagogroup.cn/api'; function fn() { // 块内部的常量使用 camelCase const defaultValue = 1; } // 当常量表示一个 react 组件或全局的单例时,使用 PascalCase� const Home = () => <h1>Hello, World!</h1>; export const Config = {};
-
6.3 一个变量一个声明语句。
// bad const num = 1, str = 'hello world', flag = true; // good const num = 1; const str = 'hello world'; const flag = true;
-
6.4 对
const
和let
声明进行分组,常量声明放置在最前,已赋值的变量其次,未赋值的变量最后。// bad let a: number; const x = 1; let b = '1'; const y = 2; // good const x = 1; const y = 2; let b = '1'; let a: number;
-
6.5 声明之前禁止使用。
-
6.6 禁止覆盖外层作用域变量。
// bad const a = 1; function fn() { const a = true; }
-
7.1 使用字面量创建对象。键值使用
camelCase
。// bad const item = new Object(); // good const item = {};
-
7.2 不要使用保留字作为键值,使用同义词进行替换。
// bad const superman = { class: 'alien', private: true, }; // bad const superman = { klass: 'alien', private: true, }; // good const superman = { type: 'alien', hidden: true, };
-
7.3 使用对象方法的简写。
// bad const atom = { value: 1, addValue: function (value) { return atom.value + value; }, }; // good const atom = { value: 1, addValue(value) { return atom.value + value; }, };
-
7.4 考虑使用对象属性值的简写。
const name = 'Luke Skywalker'; // bad const obj = { name: name, }; // good const obj = { name, };
-
7.5 如果使用了简写属性,将简写属性放置在最前。
const id = 1; const name = 'Luke Skywalker'; // bad const obj = { age: 12, id, name, }; // good const obj = { id, name, age: 12, };
-
7.6 键值需要加引号时再加引号。
// bad const user = { 'id': 1, 'first name': 'tom', }; // good const user = { id: 1, 'first name': 'tom', }; // best const user = { id: 1, firstName: 'tom', };
-
7.7 在对象有明确的类型时,使用
.
语法,对象类型是any
或索引类型时,使用['key']
语法。interface User { id: number name: string; } const user: User = { id: 1, name: 'tom', }; // bad console.log(user['name']); // good console.log(user.name); const user: any = { id: 1, name: 'tom', } console.log(user['name']); interface AnyObject { [key: string]: any; } const obj: AnyObject = {}; console.log(obj['data']);
-
7.8 考虑使用对象
...
扩展运算符代替Object.assign()
。const original = { a: 1, b: 2 }; // bad const result = Object.assign({}, original, { c: 3 }); // good const result = { ...original, c: 3 }; const result = { ...original, ...{ c: 3 } };
-
8.1 使用字面量创建数组。
// bad const items = new Array(); // good const items = [];
-
8.2 不要创建稀疏数组。
// bad const items: number[] = [1, , 2];
-
8.3 向数组添加元素时使用 Arrary#push 替代直接赋值。
const someStack = []; // bad someStack[someStack.length] = 'abracadabra'; // good someStack.push('abracadabra');
-
8.4 使用扩展运算符
...
复制数组。// bad const len = items.length; const itemsCopy = []; let i; for (i = 0; i < len; i++) { itemsCopy[i] = items[i]; } // good const itemsCopy = [...items];
-
8.5 使用 Array#from 把一个类数组对象转换成数组。
const foo = document.querySelectorAll('.foo'); const nodes = Array.from(foo);
-
8.6 使用 map,forEach,filter,reduce 等方法代替 for 循环。
const items: number[] = [1, 2, 3]; // bad const newItems: number[] = []; for (let i = 0; i < items.length; i ++) { const item = items[i] * 2; newItems.push(item); } // good const newItems = items.map(item => item * 2); // bad for (let i = 0; i < items.length; i ++) { console.log(items[i]); } // good items.forEach(item => console.log(item)); // bad let sum: number; for (let i = 0; i < items.length; i ++) { sun += items[i]; } // good const sum = items.reduce((prevSum, item) => prevSum + item);
-
8.7 在使用 pop,push,reverse 等会对数组产生突变的方法时,考虑用扩展运算符,slice() 等产生新的引用再操作,以使数据保持不可变。
const items: number[] = [1, 2, 3]; // bad items.push(4); // good const newItems = items.concat([4]); // or const newItems = [...items, 4]; // bad reverse 会改变数组后返回该引用,即 newItems 和 items 指向同一地址。 const newItems = items.reverse(); // good const newItems = items.slice().reverse(); // or const newItems = [...items].reverse();
-
8.8 当数据类型不一致但顺序和数组长度固定时考虑使用元组类型。
// bad const a: Array<string | number> = [1, 'a']; // good interface NumStrTuple extends Array<number | string> { 0: number; 1: string; length: 2; } const a: NumStrTuple = [1, 'a']; const a: NumStrTuple = ['a', 1]; // error
-
9.1 使用字面量创建
number
和string
。 -
9.2 保留小数点之前的 0,不要保留小数末尾的 0。
// bad const a = .1; const b = 0.10; // good const c = 0.1;
-
9.3 不要过多的使用
+
号拼接字符串,考虑使用模板字符串。 -
9.4 尽量不要出现魔术字符串和魔术数字,考虑用有意义的变量代理。(数字 -1,0,1,100 等除外)。
-
9.5 使用
parseInt
取整时指定进制。 -
9.6 加号两边只能同为字符串或数字。
-
10.1 枚举使用
PascalCase
,枚举值考虑使用PascalCase
或UPPER_CASE
来与普通对象区分。 -
10.2 考虑使用常量枚举,这样编译后生成的代码更小,同时避免一些运行时性能损耗。
-
11.1 接口和类型定义使用
PascalCase
。 -
11.2 接口定义之前不要加
I
前缀。 -
11.3 考虑简单数据类型(number, string, boolean)不需要声明类型。
-
11.4 数组类型声明使用 T[]。当 T 为复杂结构时,使用 Array。
interface Data { code: number; data: any; } type NumberOrString = number | string; const numbers: number[] = [1, 2, 3]; const result: Data[] = [1, 2, 3]; // bad const items: (string | number)[] = []; const items {id: number; name: string}[] = []; // good const items: Array<string | number> = []; const items: Array<{id: number, name: string}> = [];
-
11.5 定义函数接口时考虑使用
type
而不是interface
。// bad interface Foo { (a: number, b: number): number; } // good 参数较多,类型比较复杂可以这么写 type Foo = (a: number, b: number) => number; const foo: Foo = function(a, b) { return a + b; } // 但通常情况下更建议直接写在�函数里 function foo(a: number, b: number): number { return a + b; }
-
11.6 定义方法接口考虑使用对象的方法简写。
// bad interface User { getName: () => string; } // good interface User { getName(): string; }
-
11.7 函数重载签名放在一起。
-
11.8 合并可以合并的类型声明。
// bad function foo(a: number): void; function foo(a: number, b: number): void; function bar(a: number): void; function bar(a: string): void; // good function foo(a: number, b?: number): void; function bar(a: number | string): void;
-
11.9 泛型中包含默认类型时,不用添加默认类型。
function foo<T = number>(a: T): T; // bad foo<number>(1); // good foo(1); foo<string>('text');
-
11.10 当类型是固定的几个数字或字符串时,使用字面量类型代替
string
和number
。type Position = 'left' | 'right' | 'top' | 'bottom'; // bad function foo(position: string): void; // good function foo(position: Position): void;
-
12.1 使用解构存取和使用多属性对象。
Why?因为解构能减少临时引用属性。
// bad function getFullName(user) { const firstName = user.firstName; const lastName = user.lastName; return `${firstName} ${lastName}`; } // good function getFullName(obj) { const { firstName, lastName } = obj; return `${firstName} ${lastName}`; } // best function getFullName({ firstName, lastName }) { return `${firstName} ${lastName}`; }
-
12.2 对数组使用解构赋值。
const arr = [1, 2, 3, 4]; // bad const first = arr[0]; const second = arr[1]; // good const [first, second] = arr;
-
12.3 需要回传多个值时,使用对象解构,而不是数组解构。
Why?增加属性或者改变排序不会改变调用时的位置。
// bad function processInput(input) { // then a miracle occurs return [left, right, top, bottom]; } // 调用时需要考虑回调数据的顺序。 const [left, __, top] = processInput(input); // good function processInput(input) { // then a miracle occurs return { left, right, top, bottom }; } // 调用时只选择需要的数据 const { left, right } = processInput(input);
-
13.1 使用函数声明代替函数表达式。函数表达式只适用于箭头函数。
Why?因为函数声明是可命名的,所以他们在调用栈中更容易被识别。此外,函数声明会把整个函数提升(hoisted),而函数表达式只会把函数的引用变量名提升。这条规则使得箭头函数可以取代函数表达式。
// bad const foo = function () { }; // good function foo() { } const foo = ()=> ({});
-
13.2 不要在
if
、while
等代码块中声明函数。// bad if (currentUser) { function test() { console.log('Nope.'); } } // good let test; if (currentUser) { test = () => { console.log('Yup.'); }; }
-
13.3 使用 rest 语法
...
代替arguments
。Why?使用
...
能明确你要传入的参数。另外 rest 参数是一个真正的数组,而arguments
是一个类数组。// bad function concatenateAll() { const args = Array.prototype.slice.call(arguments); return args.join(''); } // good function concatenateAll(...args) { return args.join(''); }
-
13.4 直接给函数的参数指定默认值,不要使用一个变化的函数参数。
// really bad function handleThings(opts) { // 不!我们不应该改变函数参数。 // 更加糟糕: 如果参数 opts 是 false 的话,它就会被设定为一个对象。 // 但这样的写法会造成一些 Bugs。 //(译注:例如当 opts 被赋值为空字符串,opts 仍然会被下一行代码设定为一个空对象。) opts = opts || {}; // ... } // still bad function handleThings(opts) { if (opts === void 0) { opts = {}; } // ... } // good function handleThings(opts = {}) { // ... }
-
13.5 直接给函数参数赋值时需要避免副作用。
Why?因为这样的写法让人感到很困惑。
let b = 1; // bad function count(a = b++) { console.log(a); } count(); // 1 count(); // 2 count(3); // 3 count(); // 3
-
13.6 禁止对函数参数重新赋值。
-
13.7 返回
Promise
的函数明确用async
标记。 -
13.8 不要使用函数来实现类,总是使用
class
来声明一个类和使用extends
实现继承。 -
13.9 当你必须使用函数表达式(或传递一个匿名函数)时,使用箭头函数符号。
Why?因为箭头函数创造了新的一个
this
执行环境(译注:参考 Arrow functions - JavaScript | MDN 和 ES6 arrow functions, syntax and lexical scoping),通常情况下都能满足你的需求,而且这样的写法更为简洁。Why不?如果你有一个相当复杂的函数,你或许可以把逻辑部分转移到一个函数声明上。
// bad [1, 2, 3].map(function (x) { const y = x + 1; return x * y; }); // good [1, 2, 3].map((x) => { const y = x + 1; return x * y; });
-
13.10 如果一个函数适合用一行写出并且只有一个参数,那就把花括号、圆括号和
return
都省略掉。如果不是,那就不要省略。Why?语法糖。在链式调用中可读性很高。
Why不?当你打算回传一个对象的时候。
// good [1, 2, 3].map(x => x * x); // good [1, 2, 3].reduce((total, n) => { return total + n; }, 0);
-
13.11 函数(方法)及其参数总是使用
camelCase
,当表示一个 react 组件时,使用PascalCase
。 -
13.12 函数的参数个数最多不要超过5个,建议保留在3个之内,当参数过多时,建议使用对象或数组传递参数。
-
14.1 所有类使用
PascalCase
,考虑一个文件一个类。 -
14.2 使用参数属性简化类的写法。
// bad class User { public id: number; public name: string; constructor(id: number, name: string) { this.id = id; this.name = name; } } // good class User { constructor(public id: number, public name: string) {} }
-
14.3 考虑对类成员进行排序。
class User { static a; static b() {} public c; protected d; private e; constructor() {} public f() {} protected g() {} private h() {} }
-
15.1 永远使用严格等
===
和!==
而不是==
和!=
。 -
15.2 在条件语句中不要使用常量表达式。
// error if (true) {}
-
15.3 禁止不必要的
else
。// error function(flag: boolean) { if (flag) { return 'hello'; } else { return 'world'; } } // good function(flag: boolean) { if (flag) { return 'hello'; } return 'world'; } // this is also ok function(flag: boolean) { if (flag) { // do something // no return, no throw } else { // do other thing } }
-
15.4 先判断错误条件,提前退出。
// error function fn() { if (true) { // aaa... // bbb... // ccc... } } // good function fn() { if (false) { return; } // aaa... // bbb... // ccc... }
-
15.5 不要在条件语句中赋值。
-
15.6 不要与字面量布尔值比较。
-
15.7 使用严格的条件表达式。
let a: string; let b: number; // error if (!a) {} if (!b) {} // good if (a !== '') {} if (b !== 0) {} // null or undefined is ok const c = null; const d = undefined; if (!c) {} if (!d) {}
-
16.1 使用
ES2015
的模块系统而不是其他。 -
16.2 考虑使用命名导出代替默认导出。
-
16.3 导入顺序遵循如下规则。
import 'hammerjs'; import * as Foo from 'foo'; import React from 'react'; import { a, b, c } from 'bar'; import '../xxx'; import * as Bar from '../bar'; import Utils from '../utils'; import { A, B, C, } from '../yyy'; import './iife'; import * as Baz from './baz'; import xyz from './xyz'; import z, { x, y } from './balabala';
-
17.1 使用
/** ... */
作为多行注释。中间的*
号之后保留一个空格。 -
17.2 使用
//
作为单行注释。//
之后需要保留一个空格,如果注释和代码在同一行内,之前也需要保留一个空格。 -
17.5 对外输出的内容尽量添加注释。