code-norm
以TypeScript为基础语言
类型
基本类型
-
基础类型赋值时,应该直接使用类型的值
基础类型包括string, number, boolean, null, undefined, symbol
复杂类型
-
复杂类型一定是const(常量)
-
复杂类型赋值其实是地址的引用
复杂类型包括object, array, function, enum(TypeScript)
状态
-
不要使用var
-
尽量使用const
-
少使用let
var是js语法中的变量声明的关键字,但是有几个缺点,其一,它没有块级作用域,其二var声明的是一个变量,变量本身意味着结构可变,但凡结构可变的数据,其本身就意味着不稳定,也意味着代码设计本身存在的缺陷。
对象
- 使用字面量创建对象
// 错误的方式
const data = new Object();
// 正确的方式
const data = {};
- 属性值缩写
const name = "abc";
// 错误的方式
const usr = {
name: name
}
// 正确的方式
const usr = {
name
}
- 需要拷贝对象时,使用…扩展运算符,而不要采用Object.assign,解构赋值获取对象的几个属性时,使用rest运算符,也是…
// very bad
const original = { a: 1, b: 2 };
const copy = Object.assign(original, { c: 3 });
delete copy.a; // so does this 改变了 original
// bad
const original = { a: 1, b: 2 };
const copy = Object.assign({}, original, { c: 3 });
// copy => { a: 1, b: 2, c: 3 }
// good
const original = { a: 1, b: 2 };
const copy = { ...original, c: 3 };
// copy => { a: 1, b: 2, c: 3 }
const { a, ...noA } = copy;
// noA => { b: 2, c: 3 }
一般而言,值的拷贝多用于react的state的复杂属性的设置。例如,state = { usr1: {name: "name", age: 20}}, 如果需要单独修改usr1的name属性,那么得先将usr1浅拷贝一份_usr1,然后修改_usr1的name属性,再将_usr1的值通过setState更新到state中的usr1上。
数组
- 字面量赋值
// bad
const items = new Array();
// good
const items = [];
- 用Array的push 向数组中添加一个值而不是直接用下标
const items = [];
// bad
item[item.length] = "abc";
// good
items.push("abc");
-
对象转换为数组
后端传递输出分为数组和Map两种,数组本身具有遍历属性,但是Map是一种特殊的格式,比如 result = {usr1: {}, usr2: {}, usr3: {}},其中usr1, usr2,...都具有完全一致的结构,这时我们可以将result转换为一个数组,数组的值就是result的属性
interface IUsr{
name: string;
age: number
}
interface IUsrMap{
[name: string]: IUsr
}
const result: IUsrMap = {
usr1: {
name: "usr_1",
age: 10
},
usr2: {
name: "usr_2",
age: 11
}
}
const result_arr: IUsr[] = [...result];
- 如果一个数组有很多行,在数组的 [ 后和 ] 前换行
// bad
const arr = [
[0, 1], [2, 3], [4, 5],
];
const objectInArray = [{
id: 1,
}, {
id: 2,
}];
const numberInArray = [
1, 2,
];
// good
const arr = [[0, 1], [2, 3], [4, 5]];
const objectInArray = [
{
id: 1,
},
{
id: 2,
},
];
const numberInArray = [
1,
2,
];
- 用字符串模板而不是 + 来拼接字符串
// bad
function sayHi(name: string){
return "Hi " + name;
}
// bad
function sayHi(name: string){
return ["Hi", name].join(" ");
}
// bad
function sayHi(name: string){
return `Hi ${ name }`;
}
// good
function sayHi(name: string){
return `Hi ${name}`;
}
函数
-
用命名函数表达式而不是函数声明
函数声明作用域会提升,降低了代码可读性和可维护性。如果你发现一个函数又大又复杂,这个函数妨碍这个文件其他部分的理解性,这可能就是时候把这个函数单独抽成一个模块了。
// bad
function foo() {
// ...
}
// bad
const foo = function () {
// ...
};
// good
const short = function longUniqueMoreDescriptiveLexicalFoo() {
// ...
};
- 永远不要用arguments命名参数。它的优先级高于每个函数作用域自带的 arguments 对象, 所以会导致函数自带的 arguments 值被覆盖。
// bad
function foo(name, options, arguments) {
// ...
}
// good
function foo(name, options, args) {
// ...
}
- 使用默认参数语法,而不是在函数里对参数重新赋值
// really bad
function handleThings(opts) {
// 虽然你想这么写, 但是这个会带来一些细微的bug
// 如果 opts 的值为 false, 它会被赋值为 {}
opts = opts || {};
// ...
}
// still bad
function handleThings(opts) {
if (opts === void 0) {
opts = {};
}
// ...
}
// good
function handleThings(opts = {}) {
// ...
}
- 使用默认参数时,需要避免副作用
var b = 1;
// bad
function count(a = b++) {
console.log(a);
}
count(); // 1
count(); // 2
count(3); // 3
count(); // 3
如果需要在执行的时候特定修改某个值的效果,比如,点击累加,点击累减,可以构造类,在类的方法中直接修改this下面的标记属性,达到此效果
- 把默认参数赋值放在最后
// bad
function handleThings(opts = {}, name) {
// ...
}
// good
function handleThings(name, opts = {}) {
// ...
}
实际上,前一个函数和后一个函数的调用结果完全不同,因为函数的参数赋值是从左到右的顺序,如果,只传一个参数,那么第一个函数中会被赋予opts变量,但是第二个函数会赋予name变量。
- 不要对参数重新赋值(*)
// bad
function f1(a) {
a = 1;
// ...
}
function f2(a) {
if (!a) { a = 1; }
// ...
}
// good
function f3(a) {
const b = a || 1;
// ...
}
function f4(a = 1) {
// ...
}
由于js是解释性语言,所以即使在函数内部直接修改函数的参数在执行中也不会直接抛出错误,但是修改参数的这种行为破坏类函数本身的稳定性。函数本身应当具有3个稳定特性,1.函数在声明之后是不变的。多个函数,需要多个声明,并且分别调用,而不是修改声明函数达到目的。2.函数的参数不允许修改。一般来说,如果我们需要借助当前参数生成新的数据结构时,应当直接构造一个新的变量,而非修改原有变量,3.函数的功能纯粹,且精炼,函数的单一执行过程越复杂相应的稳定性和可读性就会越差,当你需要完成一个复杂的功能时首先要考虑的是如何拆分功能,而后组合功能达到效果。简单说就是,每一个函数的行数越短越好,个数没有要求,3个40行的函数>2个60行的参数>120行的单个函数。(这个实际上是函数的一种优化方式)
- 如果要用匿名函数做回调,最好使用箭头函数
// 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;
});
// best
[1, 2, 3].map((x) => x*(x + 1));
类和构造函数
- 始终用class,避免直接操作prototype
// bad
function Queue(contents = []) {
this.queue = [...contents];
}
Queue.prototype.pop = function () {
const value = this.queue[0];
this.queue.splice(0, 1);
return value;
};
// good
class Queue {
constructor(contents = []) {
this.queue = [...contents];
}
pop() {
const value = this.queue[0];
this.queue.splice(0, 1);
return value;
}
}
-
使用extends实现继承, 不要直接操作prototype
-
方法可以返回this来实现方法链
// good
class Jedi {
jump() {
this.jumping = true;
return this;
}
setHeight(height) {
this.height = height;
return this;
}
}
const luke = new Jedi();
luke.jump().setHeight(20);
迭代器
-
不要用迭代器。用 JavaScript 高阶函数代替for-in、 for-of
不可变原则,处理纯函数的返回值比处理副作用更容易。 数组的迭代方法: map() / every() / filter() / find() / findIndex() / reduce() / some() / ... 对象的处理方法 :Object.keys() / Object.values() / Object.entries() 去产生一个数组, 这样你就能去遍历对象了 对象转化数组参考-> 上文中的 对象转换为数组
const numbers = [1, 2, 3, 4, 5];
// bad
let sum = 0;
for (let num of numbers) {
sum += num;
}
sum === 15;
// good
let sum = 0;
numbers.forEach(num => sum += num);
sum === 15;
// best (use the functional force)
const sum = numbers.reduce((total, num) => total + num, 0);
sum === 15;
// bad
const increasedByOne = [];
for (let i = 0; i < numbers.length; i++) {
increasedByOne.push(numbers[i] + 1);
}
// good
const increasedByOne = [];
numbers.forEach(num => increasedByOne.push(num + 1));
// best (keeping it functional)
const increasedByOne = numbers.map(num => num + 1);
属性
- 访问属性请使用点符号
const luke = {
jedi: true,
age: 28,
};
// bad
const isJedi = luke['jedi'];
// good
const isJedi = luke.jedi;
- 获取的属性是变量时用方括号[]
const luke = {
jedi: true,
age: 28,
};
function getProp(prop) {
return luke[prop];
}
const isJedi = getProp('jedi');
变量
-
始终用 const 或 let 声明变量
-
每个变量单独用一个 const 或 let
-
不要使用连续变量分配
例如let a = b = c = 1这种使用方式是错误的
-
不允许有未使用的变量
-
禁止在同一个区域内使用重复,无意义的变量名
这样会使得你的代码更加难以阅读,优秀代码的特征之一就是简单,所以不要大量使用变量名a,b,c这种,可以尝试以[数据含义+数据类型],比如站点配置-siteConfig,如果是多个站点的配置就是-siteConfigList,当然最重要的是给这个变量加上注释
比较和相等
-
使用 === 表示比较
-
对于可能为null的属性,使用if(value)来执行存在选项,或者if(!value)执行非存在选项
-
Objects => true
-
Undefined => false
-
Null => false
-
Booleans => the value of the boolean
-
Numbers
- 0, -0, or NaN => false
- 其他 => true
-
Strings
- ‘’ => false
- 其他 => true
-
switch case 中,在 case 和 default 分句里用大括号创建一个块(如:let, const,function, and class)
// bad
switch (foo) {
case 1:
let x = 1;
break;
case 2:
const y = 2;
break;
case 3:
function f() {
// ...
}
break;
default:
class C {}
}
// good
switch (foo) {
case 1: {
let x = 1;
break;
}
case 2: {
const y = 2;
break;
}
case 3: {
function f() {
// ...
}
break;
}
case 4:
bar();
break;
default: {
class C {}
}
}
switch case语法中,最好不要直接使用return直接返回,使用return直接中断整个函数导致整个函数的流程上不完整,所以最好switch只是执行分支操作,然后直接break,如果需要返回,直接在整个switch语句之后加上return
注释
- 多行注释用 /** … */
// bad
// make() returns a new element
// based on the passed in tag name
//
// @param {String} tag
// @return {Element} element
function make(tag) {
// ...
return element;
}
// good
/**
* make() returns a new element
* based on the passed-in tag name
*/
function make(tag) {
// ...
return element;
}
- 所有注释开头加一个空格,方便阅读
// bad
//is current tab
const active = true;
// good
// is current tab
const active = true;
// bad
/**
*make() returns a new element
*based on the passed-in tag name
*/
function make(tag) {
// ...
return element;
}
// good
/**
* make() returns a new element
* based on the passed-in tag name
*/
function make(tag) {
// ...
return element;
}
命名约定
- 避免用一个字母命名,让你的命名更加语义化
- 用 camelCase 命名你的对象、函数、实例
- 不要保存 this 的引用,使用箭头函数或硬绑定
// bad
function foo() {
const self = this;
return function () {
console.log(self);
};
}
// bad
function foo() {
const that = this;
return function () {
console.log(that);
};
}
// good
function foo() {
return () => {
console.log(this);
};
}
-
文件名应与默认导出(export default)的名称完全匹配
-
全大写字母定义用来导出的常量
const ADMIN = "admin" 这表示ADMIN是一个常量,这种常量一遍用于多处引用
常见问题
-
组件行数过多(> 300)
-
函数返回值类型一致(null除外)
1.函数如果在某种情况下执行结果有返回值,那么请在所有执行分支执行的结果都加上返回值 2.执行失败(比如网络请求),请加上返回值null 3.返回值的类型保持一致,如果某个分支返回true,那么所有的返回值只能是true|false,当然,如果是请求数据,请求失败时可以返回null
-
不要添加任何没有意义的返回值,在判断一个函数是否需要返回值的时候,首先需要做的是思考如果不加返回值,那么存在什么问题,这个问题是否可以在不加返回值的情况下解决