前端团队开发代码规范(js+vue)

1,024 阅读5分钟

ES6 编码规范

变量声明、常量

  • 对于只在当前作用域下有效的变量,应使用 let 声明,不再使用 var。但应避免声明过多全局变量污染环境
  • 对于常量应使用 const 进行声明,命名应遵循字母全大写的通俗约定。对于使用 immutable 数据应用 const 进行声明。
  • 注意: const 与 let 只在声明所在的块级作用域内有效
// 不好
const someNum = 123;
const AnotherStr = "不变的字符串";
let SOME_ARR = ["不", "变", "数", "组"];
var ANOTHER_OBJ = { 不变对象: true };

// 好
const SOME_NUM = 123;
const ANOTHER_STR = "不变的字符串";
const SOME_ARR = ["不", "变", "数", "组"];
const ANOTHER_OBJ = { 不变对象: true };

模板字符串

对于多行字符串,拼接变量的情况适用模板字符串,以反引号( ` )标示 可读性更强,代码更易编写

// 不好
const multiStr = "多行字符串换行表示,\n这是换行了。";
function sayHi(name) {
  return "How are you, " + name + "?";
}
console.log(multiStr);
console.log(sayHi("张三"));

// 好
const multiStr1 = `多行字符串换行表示
				这是换行了。`; // 注意模板字符串中的空格有几个就算几个
function sayHi1(name) {
  return `How are you, ${name}?`;
}
console.log(multiStr1);
console.log(sayHi1("李四"));
多行字符串换行表示,
这是换行了。
How are you, 张三?
多行字符串换行表示
                       这是换行了。
How are you, 李四?

解构

1.嵌套结构的对象层数不能超过 3 层

let obj = {
  one: [
    {
      newTwo: [
        {
          three: [
            {
              four: "太多成了,头晕晕",
            },
          ],
        },
      ],
    },
  ],
};

// 好
let obj = {
  one: ["two", { four: "解析清楚" }],
};

2. 对象解构:对象解构 元素与顺序无关 。对象指定默认值时仅对恒等于 undefined ( !== null ) 的情况生效

01:若函数形参为对象时,使用对象解构赋值

// 不好
function someFun(opt) {
  let opt1 = opt.opt1;
  let opt2 = opt.opt2;
  console.log(opt);
}

// 好
function someFun(opt) {
  let { opt1, opt2 } = opt;
  console.log(`${opt1} 加上 ${opt2}`);
}

function someFun({ opt1, opt2 }) {
  console.log(opt1);
}

02:若函数有多个返回值时,使用对象解构,不使用数组解构,避免添加顺序 的问题

// 不好
function anotherFun() {
  const one = 1;
  const two = 2;
  const three = 3;
  return [one, two, three];
}
const [one, three, two] = anotherFun(); // 顺序打乱了

// 好
function anotherFun() {
  const one = 1;
  const two = 2;
  const three = 3;
  return { one, two, three };
}
const { one, three, two } = anotherFun(); // 不用管顺序

03:已声明的变量不能用于解构赋值(语法错误)

// 语法错误
let a;
{a} = {a: 123}

3.数组解构:数组元素与顺序相关

01:交换变量的值

let x = 1;
let y = 2;

// 不好
let temp;
temp = x;
x = y;
y = temp;

// 好
[x, y] = [y, x]; // 交换变量

02:将数组成员赋值给变量时,使用数组解构

const arr = [1, 2, 3, 4, 5];

//不好
const one = arr[0];
const two = arr[1];

//好
const [one, two] = arr;

数组

1.将类数组对象与可遍历对象(如 Set, Map)转为真正数组

采用 Array.from 进行转换

// 不好
function foo() {
  let args = Array.prototype.slice.call(arguments);
}

// 好
function foo() {
  let args = Array.from(arguments);
}

2.数组去重

ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。Set 本身是一个构造函数,用来生成 Set 数据结构。

数组去重

const arr = [3, 5, 2, 2, 5, 5];
const unique = [...new Set(arr)]; // [3,5,2]

结合 Set 结构与 Array.from

function deduplication(arr) {
  return Array.from(new Set(arr));
}

由数组去重想到了字符去重

let str = [...new Set("ababbc")].join("");
console.log(str); // 'abc'

另外 Set 是如此强大,因此使用 Set 可以很容易地实现并集(Union)、交集(Intersect)和差集(Difference)。

let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);

// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}

// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}

// 差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}

3.数组拷贝

采用数组扩展...形式

const items = [1, 2, 3];

// 不好
const len = items.length;
let copyTemp = [];
for (let i = 0; i < len; i++) {
  copyTemp[i] = items[i];
}

// 好
let copyTemp = [...items];

4.将一组数值转为数组

采用 Array.of 进行转换

// 不好
let arr1 = new Array(2); // [empty × 2]
let arr2 = new Array(1, 2, 3); // [1, 2, 3]

// 好
let arr1 = Array.of(2); // [2]
let arr2 = Array.of(1, 2, 3); // [1, 2, 3];

5.展平嵌套数组

Array 有一个名为 Array.flat 的方法,它需要一个表示深度的参数来展平嵌套数组(默认值为 1)。但是如果你不知道深度怎么办,这时候只需要将 Infinity 作为参数即可。另外还有一个很好用的 flatMap 方法。

const arrays = [[10], 50, [100, [2000, 3000, [40000]]]];
arrays.flat(Infinity); // [ 10, 50, 100, 2000, 3000, 40000 ]

6.数组求和

let foo = [1, 2, 3, 4, 5];

//不优雅
function sum(arr) {
  let x = 0;
  for (let i = 0; i < arr.length; i++) {
    x += arr[i];
  }
  return x;
}
sum(foo); // => 15

//优雅
foo.reduce((a, b) => a + b); // => 15

7.向数组中插入元素

可以使用push在数组尾部插入元素,可以用unshift在数组头部插入元素,也可以用splice在数组中间插入元素。

var arr = [1,2,3,4,5];

// 尾部插入
// old method
arr.push(6);

// new method 快43%
arr[arr.length] = 6; 

// 头部插入
// old method
arr.unshift(0);

// new method 快98%
[0].concat(arr);

8.排列含音节字母的字符串

Javascript有一个原生方法sort可以排列数组。一次简单的array.sort()将每一个数组元素视为字符串并按照字母表排列。但是当你试图整理一个非ASCII元素的数组时,你可能会得到一个奇怪的结果。

['Shanghai', 'New York', 'Mumbai', 'Buenos Aires'].sort();
 // ["Buenos Aires", "Mumbai", "New York", "Shanghai"] 

//method1
// 西班牙语
['único','árbol', 'cosas', 'fútbol'].sort();
// ["cosas", "fútbol", "árbol", "único"] // bad order

//method2
['único','árbol', 'cosas', 'fútbol'].sort(Intl.Collator().compare);
// ["árbol", "cosas", "fútbol", "único"]

函数

1.当要用函数表达式或匿名函数时,使用箭头函数(Arrow Functions)

箭头函数更加简洁,并且绑定了 this

// 不好
const foo = function (x) {
  console.log("存在函数提升问题");
  console.log("foo.name"); // 返回'', 函数表达式不可命名,函数声明可以
};

[1, 2, 3].forEach(function (x) {
  return x + 1;
});

// 好
const foo = (x) => {
  console.log("存在函数提升问题");
  console.log("foo.name"); // 返回 'foo'
};

[1, 2, 3].forEach((x) => {
  return x + 1;
});

01: 箭头函数书写约定

函数体只有单行语句时,允许写在同一行并去除花括号

当函数只有一个参数时,允许去除参数外层的括号

// 好
const foo = (x) => x + x; // 注意此处会默认return x + x, 有花括号语句块时不会return

[1, 2, 3].map((x) => x * x);

02:用箭头函数返回一个对象,应用括号包裹

// 不好
let test = (x) => {
  x: x;
}; // 花括号会变成语句块,不表示对象
// 好
let test = (x) => ({ x: x }); // 使用括号可正确return {x:x}

2.立即调用函数 IIFE

使用箭头函数

// 不好
(function () {
  console.log("哈");
})();

// 好
(() => {
  console.log("哈");
})();

3.不使用 arguments, 采用 rest 语法...代替

rest 参数是真正的数组,不需要再转换 注意:箭头函数中不能使用 arguments 对象

// 不好
function foo() {
  let args = Array.prototype.slice.call(arguments);
  return args.join("");
}

// 好
function foo(...args) {
  return args.join("");
}

4.函数参数指定默认值

采用函数默认参数赋值语法

// 不好
function foo(opts) {
  lopts = opts || {}; // 此处有将0, ''等价值转换掉为默认值的副作用
}

// 好
function foo(opts = {}) {
  console.log("更加简洁,安全");
}

5.对象中的函数方法使用缩写形式

更加简洁

// 不好
const shopObj = {
    desc: '对象模块写法',
    foo: function() {
		console.log('对象中的方法')
	};
};

// 好
const shopObj = {
    desc: '对象模块写法',
    foo() {
		console.log('对象中的方法')
	};
};

6.函数式的方式处理数据

函数式编程主张函数必须接受至少一个参数并返回一个值。所以所有的关于数据的操作,都可以用函数式的方式处理。假设我们有这样的需求,需要先把数组 foo 中的对象结构更改,然后从中挑选出一些符合条件的对象,并且把这些对象放进新数组 result 里。

let foo = [
  {
    name: "Stark",
    age: 21
  },
  {
    name: "Jarvis",
    age: 20
  },
  {
    name: "Pepper",
    age: 16
  }
];

//我们希望得到结构稍微不同,age大于16的对象:
let result = [
  {
    person: {
      name: "Stark",
      age: 21
    },
    friends: []
  },
  {
    person: {
      name: "Jarvis",
      age: 20
    },
    friends: []
  }
]
 
// 从直觉上我们很容易写出这样的代码: 
let result = [];
//有时甚至是普通的for循环
foo.forEach(function(person){
    if(person.age > 16){
        let newItem = {
            person: person,
            friends: [];
        };
        result.push(newItem);
    }
})
 
// 使用函数式的写法,可以优雅得多
 
let result = foo
  .filter(person => person.age > 16)
  .map(person => ({
    person: person,
    friends: []
  }));

1.类名应使用帕斯卡写法(PascalCased)

class SomeClass {}

2.定义类时,类中的方法的顺序如下:

constructor

public get/set 公用访问器,set 只能传一个参数

public methods 公用方法,以函数命名区分,不带下划线

private get/set 私有访问器,私有相关命名应加上下划线_为前缀

provate methods 私有方法

class SomeClass {
  constructor() {
    // constructor
  }

  get aval() {
    // public getter
  }

  set aval(val) {
    // public setter
  }

  doSth() {
    // 公用方法
  }

  get _aval() {
    // private getter
  }

  set _aval(val) {
    // private setter
  }

  _doSth() {
    // 私有方法
  }
}

3.如果不是 class 类,不使用 new

// 不好
function Foo() {
}
const foo = new Foo();

// 好
class Foo() {
}
const foo = new Foo();

4.使用真正意思上的类 Class 写法,不使用 prototype 进行模拟扩展

// 不好
function Dog(names = []) {
    this._names = [...names];
}
Dogs.prototype.bark = function() {
    const curName = this._names[0];
    alert(`one one ${curName}`);
}

// 好
class Dog() {
    constructor(names = []){
        this._names = [...names];
	}
    bark() {
        const curName = this._names[0];
        alert(`one one ${curName}`);
    }
}

5.class 应先定义后使用

虽然规范里 class 不存在 hoist 问题,但转换工具如 babel,只是转换为函数表达 式,此处仍有 hoist 使用继承时,应先定义父类再定义子类

// 不好
let foo = new Foo();
class Foo {}

// 好
class Foo {}
let foo = new Foo();
class SubFoo extends Foo {}

6.this 的注意事项

子类使用 super 关键字时,this 应在调用 super 之后才能使用 可在方法中 return this 来实现链式调用写法

class Foo {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

模块

1.使用 CommonJs(import / export)来做模块加载导出,不使用其他写法

跟着标准走的人,运气总不会太差

// bad
const vars = require("vars");
module.exports = vars.red;

// good
import { red } from "vars";
export default red;

2.应确保每个 module 有且只有一个默认导出模块

方便调用方使用

// bad
const red = '#ff0000';
export red;

// good
const red = '#ff0000';
export default red;

3.import 不使用统配符 * 进行整体导入

确保模块与模块之间的关系比较清晰

// bad
import * as vars from "vars";

// good
import vars from "vars";

4.不要将 import 与 export 混合在一行

分开导入与导出,让结构更清晰,可读性更强

// bad
export {red as default} from './varColors';

// good
import {red} from './varColors';
export default red;

5.多变量要导出时应采用对象解构形式

export 置于底部,使欲导出变量更加清晰

// bad
export const red = "#00ff00";
export const black = "#00ff00";
export const white = "#00ff00";

// good
const red = "#00ff00";
const black = "#00ff00";
const white = "#00ff00";

export default { red, black, white };

Map 与字典类型数据

JavaScript 实现字典数据是基于 Object 对象。但是 JavaScript 的对象的键只能是字符串。对于编程来说有很多不便。 ES6 提供了 Map 数据结构。它类似于 Object 对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值,字符串、数值、布尔值、数组、对象等等都可以当作键。

const resultMap = new Map()
  .set(-1, {text:'小于',color:'yellow')
  .set(0, {text:'等于',color:'black')
  .set(1, {text:'大于',color:'green')
  .set(null,{text:'没有物品',color:'red'})

let state = resultMap.get(null) // {text:'没有物品',color:'red'}

Map 的遍历顺序就是插入顺序

const map = new Map([["F", "no"], ["T", "yes"]]);

for (let key of map.keys) {
  console.log(key);
}
// "F"
// "T"

for (let value of map.value()) {
  console.log(value);
}
// "no"
// "yes"

其他

1.条件表达式

要判断某个变量是否为指定的某些值,用常规的逻辑表达式会很长。我的做法是把这些值放进数组里:

if (['abc', 'def', 'ghi', 'jkl'].includes(x)) {
   //其他逻辑
}

2.简化if...else

if...else太常用了,以至于很多人都忘了其实还有其他方式来判断条件。比如简单的变量赋值,完全没必要用这种冗长的语句,一行表达式就搞定了:

let test = x > 10; 
// 三元表达式可以做复杂的条件分支判断,不过牺牲了一些可读性:
let x = 300,
let test2 = (x > 100) ? 'greater 100' : (x < 50) ? 'less 50' : 'between 50 and 100;

3.判空并赋默认值

判断变量不是null,undefined,''的时候赋值给第二个变量,否则给个默认值:

if (first !== null || first !== undefined || first !== '') {
    let second = first;
}
// 简写 let second = first || '';

4. 简化switch

这个技巧也很常用,把switch 转换成对象的key-value形式,代码简洁多了:

// 不好
switch (data) {
  case 1:
    test1();
    break;  
  case 2:    
    test2();  
    break;  
  case 3:    
    test();  
    break;  // And so on...}
// Shorthand
var data = {
  1: test1,
  2: test2,
  3: test
};

// 好
data[anything] && data[anything]();

5. 简化分支条件语句

// 不好
if (type === 'test1') {
002  test1();
}
else if (type === 'test2') {
  test2();
}
else if (type === 'test3') {
  test3();
}
else if (type === 'test4') {
  test4();
} else {
  throw new Error('Invalid value ' + type);
}

// 好
var types = {
  test1: test1,
  test2: test2,
  test3: test3,
  test4: test4
};
var func = types[type];
(!func) && throw new Error('Invalid value ' + type); func();

5.重复字符串N次

有时候出于某种目的需要将字符串重复 N 次,最笨的方法就是用for循环拼接。其实更简单的方法是用repeat:

// 不好 
let test = ''; 
for(let i = 0; i < 5; i ++) { 
  test += 'test '; 
} 
console.log(str); // test test test test test 
// 好 
'test '.repeat(5);

Vue 代码规范

html 多属性时换行

和 html 类型,当某行的属性很多,适当的换行可以提高阅读和美观

.custom-style {
  //可以在一次声明中定义一个或多个属性
  background: background-clip background-color background-image
    background-origin background-position background-repeat background-size;
}

Css

css 嵌套层级

浏览器在解析 css 时,是按照从右到左递归匹配的,过深的层级嵌套不仅影响 性能,而且还会导致样式阅读性和代码维护性降低,一般层架控制在 5 层之内

双引号优先

属性选择器中的值必须用双引号包围,不允许使用单引号,也不允许不使用引号,html 的属性值也是推荐使用双引号

.custom-style {
  font-family: "PingFang SC", "STHeitSC-Light";
}

选择器与 { 空格

.costom-style { // 选择器和{ 之间空格
	margin: 0; // 属性值前
	transform: scale(1.5, 2.2); // 逗号之后增加空格
}

共用样式

.custom .header .title,
.other .header .title {
	color: #f0f;
}

css 属性顺序

同一 规则下的属性在书写时,应按功能进行分组。并以 Formatting Model(布局方式、位置) > Box Model(尺寸) > Typographic(文本相关) > Visual(视觉效果) 的顺序书写,以提高代码的可读性。

解释:

Formatting Model 相关属性包括:position / top / right / bottom / left / float / display / overflow 等

Box Model 相关属性包括:border / margin / padding / width / height 等

Typographic 相关属性包括:font / line-height / text-align / word-wrap 等

Visual 相关属性包括:background / color / transition / list-style 等

另外,为增加可读性,如果包含 content 属性,应放在属性的最前面。

样式穿透

在开发中修改第三方组件样式是很常见,但由于 scoped 属性的样式隔离,可能需要去除 scoped 或是另起一个 style 。这些做法都会带来副作用(组件 样式污染、不够优雅),样式穿透在 css 预处理器中使用才生效。

less 使用 /deep/

<style lang="less" scoped>
.content /deep/ .el-button {
    height: 60px;
}
</style>

scss 使用 ::v-deep

<style lang="scss" scoped>
.content ::v-deep .el-button {
    height: 60px;
}
</style>

stylus 使用 >>>

<style lang="stylus" scoped>
外层 >>> .custom-components {
    height: 60px;
}
</style>

less根据返回值对样式进行修改

> p {
    color: #15eee0;
    font-size: 36px;
    font-weight: bold;

    .d2 {
        color: #ece462;
    }
    .d3 {
        color: #ff9154;
    }
    .d4 {
        color: #ff2927;
    }
}
// 此时assess可选2,3,4
<p>
    注意状态,且已到
    <span :class="$style[`d${assess}`]">哈哈哈</span>
    的预警线
</p>

Vue 开发

和渲染无关的数据

vue 中 data 的数据默认便会进行双向数据绑定,若是将大量的和渲染无关的 数据直接放置在 data 中,将会浪费双向数据绑定时所消耗的性能,将这些和渲染无关的数据进行抽离并配合 Object.freeze 进行处理!

例如:table 中 columns 数据可以单独提取一个外部 js 文件作为配置文件,也可以 在当前 vue 文件中定义一个常量定义 columns 数据,因为无论如何都是固定 且不会修改的数据,应该使用 Object.freeze 进行包裹,既可以提高性能还可 以将固定的数据抽离,一些下拉框前端固定的数据也建议此操作

const columnList = Object.freeze([
  {
    title: "姓名",
    key: "name",
    align: "center",
  },
  {
    title: "性别",
    key: "gender",
    align: "center",
  },
]);

需要注意的是 Object.freeze() 冻结的是值,这时仍然可以将变量的引用替换掉,还有确保数据不会变才可以使用这个语法,如果要对数据进行修改和交互,就不适合使用冻结了。

Modal 框的控制

一个页面种通常会存在很多个不同功能的弹框,若是每一个弹框都设置一个对应的变量来控制其显示,则会导致变量数量比较冗余和命名困难,可以使用一 个变量来控制同一页面中的所有 Modal 弹框的展示

比如某个页面中存在三个 Modal 弹框

// bad
// 每一个数据控制对应的Modal展示与隐藏
new Vue({
  data() {
    return {
      modal1: false,
      modal2: false,
      modal3: false,
    };
  },
});

// good
// 当modalType为对应的值时,展示其对应的弹框
new Vue({
  data() {
    return {
      modalType: "", // modalType值为 modal1, modal2, modal3
    };
  },
});

debounce 使用

例如:远程搜索时需要通过接口动态的获取数据,若是每次用户输入都接口请求,是浪费带宽和性能的,当一个按钮多次点击时会导致多次触发事件,可以结合场景做节流

import {debounce} from 'lodash'

method: {
    remoteMethod: debounce(function (query) {
     	//todo
     	//this 的指向没有问题
    },200),
}

图片的使用

功能的开发过程中,图片的处理往往是比较容易被忽略的环节,也会在一定程度影响开发的效率和页面的性能

1.图片压缩问题,除非特别要求图片必须高质量的显示,否则都应该进行对应的压缩处理

2.不同业务场景进行图片格式的选型

01:JPG : 适用于呈现色彩丰富的图片,JPG 图片经常作为大的背景图、轮播 图或 Banner 图出现等

02:PNG: Logo、颜色简单且对比强烈的图片或背景、需要透明度等

03:将常用且变动频率很低的小图片进行合并成雪碧图,对于变动比较频繁 和小于 6KB 的图片进行 base64 处理

04:根据项目图片数量和项目的用户机型分布等,考虑采取 webp 进行图片 的处理

路由组件传参

在组件中使用 $route 会使之与其对应路由形成高度耦合,从而使组件只能在某些特定的 URL 上使用,限制了其灵活性

使用 props 将组件和路由解耦:

取代与 $route 的耦合

const User = {
  template: "<div>User {{ $route.params.id }}</div>",
};
const router = new VueRouter({
  routes: [{ path: "/user/:id", component: User }],
});

通过 props 解耦

这样你便可以在任何地方使用该组件,使得该组件更易于重用和测试。

const User = {
  props: ["id"],
  template: "<div>User {{ id }}</div>",
};
const router = new VueRouter({
  routes: [
    { path: "/user/:id", component: User, props: true },

    // 对于包含命名视图的路由,你必须分别为每个命名视图添加 `props` 选项:
    {
      path: "/user/:id",
      components: { default: User, sidebar: Sidebar },
      props: { default: true, sidebar: false },
    },
  ],
});

Select 优化

下拉框遍历时,需要注意 options 标签保持同一行,若是存在换行,会导致选 中时的值存在多余的空白

<!-- bad -->
<Select :remote-method="remoteMethod">
    <Option v-for="item in temoteList" :value="item.value" :key="item.id">
        {{item.label}}
    </Option>
</Select>

需要将 Options 和下拉框的值保持在同一行

<!-- good -->
<Select :remote-method="remoteMethod">
    <Option v-for="item in temoteList" :value="item.value" :key="item.id">			{{item.label}}
    </Option>
</Select>

data 数据层级

data 数据具有数据层级结构,切勿过度扁平化或者嵌套层级过深,若是过度 扁平化会导致数据命名空间冲突,参数传递和处理,若是层级嵌套过深也会导 致 vue 数据劫持的时候递归层级过深,小心递归爆栈的问题。而且层级过深会导致数据操作和处理不便,获取数据做容错处 理也比较繁琐。一般层级保持 2-3 层最好。

若是只有一层数据,过于扁平

{
    name: '',
    age: '',
    gender: ''
}

导致处理不方便

// 作为接口参数传递
ajax({
 this.name, this.age, this.gender
})

// 接口获取数据,批量处理
ajax().then(res => {
 const {name, age, gender} = res.data
    this.name = name
    this.age = age
    this.gender = gender
})

适当的层级结构不仅增加代码的维护和阅读性,还可以增加操作和处理的便捷性

{
    person: { // 个人信息
        name: '',
        age: '',
        gender: ''
    }
}

可以针对 person 进行操作

// 作为接口参数传递
ajax(this.person);

// 接口获取数据,批量处理
ajax().then((res) => {
  const { name, age, gender } = res.data;
  this.$set(this, "person", { name, age, gender });
});

策略模式

策略模式的使用,避免过多的 if else 判断,也可以替代简单逻辑的 switch

const formatDemandItemType = (value) => {
  switch (value) {
    case 1:
      return "基础";
    case 2:
      return "高级";
    case 3:
      return "VIP";
  }
};

// 策略模式
const formatDemandItemType2 = (value) => {
  const obj = {
    1: "基础",
    2: "高级",
    3: "VIP",
  };

  return obj[value];
};

解构

解构赋值以及默认值,当解构的数量小于多少时适合直接解构并赋值默认值, 数据是否进行相关的聚合处理

const { name = "", age = 10, gender = "man" } = res.data;

// bad
this.name = name;
this.age = age;
this.gender = gender;

// good
this.person = {
  name,
  age,
  gender,
};

单一职责原则

任何时候尽量是的一个函数就做一件事情,而不是将各种逻辑全部耦合在一 起,提高单个函数的复用性和可读性

created() {
  this.init();
},
methods: {
  // 将全部的请求行为聚合在init函数中
  // 将每个请求单独拆分
  init() {
    this.getList1()
    this.getList2()
  },
  getList1() {
    // to do ...
  },
  getList2() {
    // to do ...
  }
}

为 v-for 设置键值

总是用 key 配合 v-for。 在组件上总是必须用 key 配合 v-for,以便维护内部组件及其子树的状态。甚至在元素上维护可预测的行为,比如动画中的对象固化 (object constancy),也是一种好的做法。

// 正例:
<ul>
	<li
		v-for="todo in todos"
		:key='todo.id'
	>
		{{ todo.text }}
	</li>
</ul>

// 反例:
<ul>
	<li
		v-for="todo in todos">
		{{ todo.text }}
	</li>
</ul>

避免 v-if 和 v-for 用在一起

永远不要把 v-if 和 v-for 同时用在同一个元素上。一般我们在两种常见的情况下会倾向于这样做:

  • 01:为了过滤一个列表中的项目 (比如 v-for="user in users" v-if="user.isActive")。在这种情形下,请将 users 替换为一个计算属性 (比如 activeUsers),让其返回过滤后的列表。
  • 02:为了避免渲染本应该被隐藏的列表 (比如 v-for="user in users" v-if="shouldShowUsers")。这种情形下,请将 v-if 移动至容器元素上 (比如 ul, ol)。
// 正例:
<ul v-if="sshouldShowUsers">
	<li
		v-for="user in users"
		:key='user.id'
	>
		{{ user.name }}
	</li>
</ul>

// 反例:
<ul>
	<li
		v-for="user in users"
		:key='user.id'
		v-if="sshouldShowUsers"
	>
		{{ user.name }}
	</li>
</ul>

为组件样式设置作用域

对于应用来说,顶级 App 组件和布局组件中的样式可以是全局的,但是其它所有组件都应该是有作用域的。 这条规则只和单文件组件有关。你不一定要使用 scoped 特性。设置作用域也可以通过 CSS Modules。 不管怎样,对于组件库,我们应该更倾向于选用基于 class 的策略而不是 scoped 特性
这让覆写内部样式更容易:使用了常人可理解的 class 名称且没有太高的选择器优先级,而且不太会导致冲突。

// 正例:
<template>
	<button class="c-Button c-Button--close">X</button>
</template>

<!-- 使用BEM约定 -->
<style>
.c-Button {
    border: none;
    border-radius: 2px;
}

.c-Button--close {
	background-color: red
}
</style>

// 反例:
<template>
	<button class="btn btn-close">X</button>
</template>

<style>
.btn-close {
	background-color: red
}
</style>

<template>
	<button class="button button-close">X</button>
</template>

<style scoped>
.button {
    border: none;
    border-radius: 2px;
}

.button-close {
	background-color: red
}
</style>

全局状态管理

应该优先通过 Vuex 管理全局状态,而不是通过 this.$root 或一个全局事件总线。

  • 但是不能绝对化的认为全局事件总线就不能使用
正例:
// store/modules/todos.js
export default {
  state: {
    list: []
  },
  mutations: {
    REMOVE_TODO (state, todoId) {
      state.list = state.list.filter(todo => todo.id !== todoId)
    }
  },
  actions: {
    removeTodo ({ commit, state }, todo) {
      commit('REMOVE_TODO', todo.id)
    }
  }
}
<!-- TodoItem.vue -->
<template>
  <span>
    {{ todo.text }}
    <button @click="removeTodo(todo)">
      X
    </button>
  </span>
</template>

<script>
import { mapActions } from 'vuex'

export default {
  props: {
    todo: {
      type: Object,
      required: true
    }
  },
  methods: mapActions(['removeTodo'])
}
</script>
反例:
// main.js
new Vue({
  data: {
    todos: []
  },
  created: function () {
    this.$on('remove-todo', this.removeTodo)
  },
  methods: {
    removeTodo: function (todo) {
      var todoIdToRemove = todo.id
      this.todos = this.todos.filter(function (todo) {
        return todo.id !== todoIdToRemove
      })
    }
  }
})

template 中表达式简单化

组件模板应该只包含简单的表达式,复杂的表达式则应该重构为计算属性或方法。 复杂表达式会让你的模板变得不那么声明式。我们应该尽量描述应该出现的是什么,而非如何计算那个值。而且计算属性和方法使得代码可以重用。

// 正例:
<!-- 在模板中 -->
{{normalizedFullName}}
// 复杂表达式已经移入一个计算属性
computed: {
    normalizedFullName: function() {
        return this.fullName.split(' ').map(function(word) {
            return word[0].toUpperCase() + word.slice(1)
        }).join(' ');
    }
}

// 反例:
{{
    fullName.split(' ').map(function(word) {
        return word[0].toUpperCase() + word.slice(1)
    }).join(' ');
}}

计算属性要尽量简单

// 正例:
computed: {
    basePrice: function() {
        return this.manufactureCost / (1- this.profitMargin);
    },
    discount: function() {
        return this.basePrice * (this.discountPercent ||0);
    },
    finalPrice: function() {
        return this.basePrice - this.discount;
    }
}

// 反例:
computed: {
    price: function() {
        var basePrice = this.manufactureCost / (1- this.profitMargin);
        return (
            basePrice - basePrice * (this.discountPercent ||0);
        )
    }
}

指令缩写

都用指令缩写 (用 : 表示 v-bind: 和用 @ 表示 v-on:)

// 正例:
<input @input="onInput" @focus="onFocus"></input>

// 反例:
<input v-bind:value="newTodoText" :placeholder="newTodoInstructions"></input>

单文件组件的顶级元素的顺序

单文件组件应该总是让

<!-- ComponentA.vue -->
<template>...</template>
<script> /* ... */ </script>
<style> /* ... */ </style>

尽量在 v-if/v-if-else/v-else 中使用 key

如果一组 v-if + v-else 的元素类型相同,最好使用 key (比如两个

元素)。

// 正例:
<div v-if="error" key="search-status">
    错误: {{ error }}
</div>
<div v-else key="search-results">
    {{ results }}
</div>

// 反例:
<div v-if="error">
    错误: {{ error }}
</div>
<div v-else>
    {{ results }}
</div>

scoped 中的元素选择器

元素选择器应该避免在 scoped 中出现。 在 scoped 样式中,类选择器比元素选择器更好,因为大量使用元素选择器是很慢的。

// 正例:
<template>
	<button class="btn btn-close">X</button>
</template>

<style scoped>
.btn-close {
	background-color: red
}
</style>

// 反例:
<template>
	<button>X</button>
</template>

<style scoped>
button {
	background-color: red
}
</style>

表格中使用form表单验证

<el-form :class="$style.form" :model="ruleInfo" label-width="12px" ref="form"> 
    <el-table :data="ruleInfo.list">
         <el-table-column label="预警值">
            <template slot-scope="scope">
                <el-form-item label=" " :prop="`list.${scope.$index}.value`"
                   :rules="[
                     { required: true, message: '预警值不能为空', type:                                 'number', trigger: 'blur' },
                     { validator: (rule, value, callback) => valueValidate(                            scope.row,value, callback), trigger: 'blur' }
                   ]">
                </el-form-item>
            </template>
         </el-table-column>
    </el-table>
</el-form>
 valueValidate(item, value, callback) {
     if () {
     	callback(new Error(''));
     } else {
     	callback();
     }
 }

组件化思想

只要有能够拼接文件的构建系统,就把每个组件单独分成文件。当你需要编辑一个组件或查阅一个组件的用法时,可以更快速的找到它。

// 正例:
components/
|- TodoList.vue
|- TodoItem.vue

// 反例:
Vue.component('TodoList', {
	// ...
})

Vue.component('TodoItem', {
	// ...
})

Vue 父子组件的生命周期

在父子组件中,掌握父子组件对应的生命周期钩子加载顺序可以让开发者在更合适的时候做适合的事情父组件

<template>
  <div>
    <h3>home</h3>
    <list @hook:mounted="listMounted" />
  </div>
</template>

<script>
import List from './list'

export default {
  name: "home",
  components: {
    List
  },
  methods: {
    listMounted(){
      console.log('------------ listMounted');
    }
  },
  beforeCreate() {
    console.log("home beforeCreate");
  },
  created() {
    console.log("home created");
  },
  beforeMount() {
    console.log("home beforeMount");
  },
  mounted() {
    console.log("home mounted");
  },
  beforeDestroy() {
    console.log("home beforeDestroy");
  },
  destroyed() {
    console.log("home destroyed");
  }
}
</script>

子组件

<template>
  <div>
    list
  </div>
</template>

<script>
export default {
  naem: "list",
  beforeCreate() {
    console.log("list beforeCreate");
  },
  created() {
    console.log("list created");
  },
  beforeMount() {
    console.log("list beforeMount");
  },
  mounted() {
    console.log("list mounted");
  },
  beforeDestroy() {
    console.log("list beforeDestroy");
  },
  destroyed() {
    console.log("list destroyed");
  }
}
</script>

加载时父子组件的加载顺序

home beforeCreate --> home created --> home beforeMount --> list created --> list beforeMount --> list mounted

销毁时父子组件的销毁顺序

home beforeDestroy --> list beforeDestroy --> list destroyed --> home destroyed

实际开发过程中会遇到当子组件某个生命周期完成之后通知父组件,然后在父组件做对应的处理

emit up

// 子组件在对应的钩子中发布事件
created(){
  this.$emit('done')
}

// 父组件订阅其方发
<list @done="childDone">

hook

通过@hook监听子组件的生命周期

<list @hook:mounted="listMounted" />

组件名为多个单词

组件名应该始终是多个单词的,根组件 App 除外。

// 正例
export default {
	name: 'TodoItem',
	// ...
}

// 反例
export default {
	name: 'Todo',
	// ...
}

组件数据 data 为函数

组件的 data 必须是一个函数。当在组件中使用 data 属性的时候 (除了 new Vue 外的任何地方),它的值必须是返回一个对象的函数。

// 正例
// In a.vue file
export default {
    data() {
        return {
            foo: 'bar'
        }
    }
}
// 在一个vue的根实例上直接使用对象是可以的,因为只存在一个这样的实例
new Vue({
    data: {
        foo: 'bar'
    }
})

// 反例
export default {
    data: {
        foo: 'bar'
    }
}

Prop 定义

Prop 定义应该尽量详细。 在你提交的代码中,prop 的定义应该尽量详细,至少需要指定其类型。

// 正例
props: {
    status: String
}
// 有更好做法:
props: {
    status: {
        type: String,
        required: true,
        validator: function(value) {
            return [
                'syncing',
                'synced',
                'version-conflict',
                'error'].indexOf(value) !== -1
            ]
        }
    }
}

// 反例
// 这样做只有开发原型系统可接受
props: ['status']

组件明明首字母大写

单文件组件的文件名应该要么始终是单词大写开头 (PascalCase)

// 正例:
components/
|- MyComponent.vue

// 反例:
components/
|- myComponent.vue
|- mycomponent.vue

基础组件名需特定

应用特定样式和约定的基础组件 (也就是展示类的、无逻辑的或无状态的组件) 应该全部以一个特定的前缀开头,比如 Base、App 或 V。

// 正例:
components/
|- BaseButton.vue
|- BaseTable.vue
|- BaseIcon.vue

// 反例:
components/
|- MyButton.vue
|- VueTable.vue
|- Icon.vue

单例组件名

只应该拥有单个活跃实例的组件应该以 The 前缀命名,以示其唯一性。 这不意味着组件只可用于一个单页面,而是每个页面只使用一次。这些组件永远不接受任何 prop,因为它们是为你的应用定制的,而不是它们在你的应用中的上下文。如果你发现有必要添加 prop,那就表明这实际上是一个可复用的组件,只是目前在每个页面里只使用一次。

// 正例:
components/
|- TheHeading.vue
|- TheSidebar.vue

// 反例:
components/
|- Heading.vue
|- MySlidebar.vue

紧密耦合的组件名

和父组件紧密耦合的子组件应该以父组件名作为前缀命名。 如果一个组件只在某个父组件的场景下有意义,这层关系应该体现在其名字上。因为编辑器通常会按字母顺序组织文件,所以这样做可以把相关联的文件排在一起。

// 正例:
components/
|- TodoList.vue
|- TodoListItem.vue
|- TodoListItemButton.vue

components/
|- SearchSidebar.vue
|- SearchSidebarNavigation.vue

// 反例:
components/
|- SearchSidebar.vue
|- NavigationForSearchSidebar.vue

组件命名中的单词顺序

组件名应该以高级别的单词开头,以描述性的修饰词结尾。

// 正例:
components/
|- SearchButtonClear.vue
|- SearchButtonRun.vue
|- SearchInputQuery.vue
|- SearchInputExcludeGlob.vue
|- SettingsCheckboxTerms.vue
|- SettingsCheckboxLaunchOnStartup.vue

// 反例:
components/
|- ClearSearchButton.vue
|- ExcludeFromSearchInput.vue
|- LaunchOnStartupCheckbox.vue
|- RunSearchButton.vue
|- SearchInput.vue
|- TermsCheckbox.vue

模板中的组件名总是 PascalCase 的

// 正例:
<!-- 在单文件组件和字符串模板中 -->
<MyComponent />

// 反例:
<!-- 在单文件组件和字符串模板中 -->
<mycomponent />
<!-- 在单文件组件和字符串模板中 -->
<myComponent />

组件名应该倾向于完整单词而不是缩写

// 正例:
components/
|- StudentDashboardSettings.vue
|- UserProfileOptions.vue

// 反例:
components/
|- SdSettings.vue
|- UProfOpts.vue

父子组件通信

应该优先通过 prop 和事件进行父子组件之间的通信,而不是 this.$parent 或改变 prop。

  • 注意上面说的是**“优先”**,而不是绝对

其他代码规范

  1. 本地不要存储xxx.copy文件: 及时删除无用代码,git上会留存历史纪录,不需要在本地保存copy版本和无用代码;

  2. 清除无用css: 页面结构调整之后,如某个div内部内容被抽离为组件,放在了其他vue文件中,那么要记得把原文件的css也删除;

  3. 减少html无用嵌套: 如下面只需要一层就搞定了,套了三层;css也跟着套了三层

    	<div :class="$style.ehvOnline">
    		<div :class="$style.main">
    			<!-- 右侧设备具体信息 -->
    			<div :class="$style.mainRight">
    				<div :class="$style.equBox">
    					<div :class="$style.headTitle">
    						<span :class="$style.equName">设备名称:</span>
    						<span :class="$style.equValue">{{ equName }}</span>
    					</div>
    					<big-tab ref="bigTabComp" :options="tabConf" @tabClick="tabNameClick"></big-tab>
    				</div>
    				<div :class="$style.imgBox">
    					<component :is="currentComp" :current-node="currentNode"></component>
    				</div>
    			</div>
    		</div>
    	</div>
    	
    	
     <style lang="less" module>
    .ehvOnline {
    	width: calc(100% - 318px);
    	height: 98%;
    	display: flex;
    	flex-direction: column;
    	border-radius: 3px;
    	.main {
    		width: 100%;
    		height: 850px;
    		display: flex;
    		background-color: #01373b;
    		.mainRight {
    			flex: 1;
    			height: 100%;
    			.equBox {
    				.headTitle {
    					height: 42px;
    					line-height: 42px;
    					.equName {
    						font-size: 18px;
    						color: #30f0e3;
    						font-weight: bold;
    						margin-right: 10px;
    					}
    					.equValue {
    						font-size: 16px;
    						color: #30f0e3;
    						margin-right: 50px;
    					}
    				}
    			}
    			.imgBox {
    				height: 730px;
    				overflow: auto;
    			}
    		}
    	}
    }
    </style>
    
  4. 组件拆分和css布局要以业务为导向,否则就会增加数据交互和组件数据交互的复杂度;

  5. css 选择器的合理利用

    <div :class="$style.atlasBody">
        <div :class="$style.leftBox"><img src="@/assets/images/dBone.png" alt="" /></div>
        <div :class="$style.rightBox"><img src="@/assets/images/dBtwo.png" alt="" /></div>
    </div>
    
    .atlasBody {
        height: calc(100% - 40px);
        display: flex;
    
        .leftBox {
            width: 734px;
            margin-right: 10px;
        }
        .rightBox {
        	flex: 1;
        }
        .leftBox,
        .rightBox {
            background: #042f30;
            position: relative;
            > img {
                width: 80%;
                height: 80%;
                position: absolute;
                top: 50px;
                left: 50px;
            }
        }
    }
    

    是不是可以这样写:

    .atlasBody {
        height: calc(100% - 40px);
        display: flex;
        > div {
        	background: #042f30;
        }
        .leftBox {
       	    margin-right: 10px;
        }
        img {
        	height: 100%;
        }
    }
    
  6. 有效的注释

    下面的2,6 就是无效注释,没有任何意义,写不写都能看出来是传递了参数

    	// 1.从新渲染设备名称 以及 设备id
        data.equName = newVal.equName;
        data.equId = newVal.equId;
        // 2.传递参数
        currentNode.value = { equId: newVal.equId, phaseList: newVal.newPhaseList };
        // 3.tab 切换
        bigTabComp.value && bigTabComp.value.resetTab();
        // 4.监听传递过来的参数(equId) 重新渲染表格
        isShowColum.value = false;
        getCheckboxColumList(newCols);
        queryTableBodyData(newProps.currentNode?.equId);
        // 5.监听传递过来的 当前设备下的相位信息
        tabPhaseData.value = newProps.currentNode?.phaseList;
        // 6.声明一个name变量  
        const name = ref('')
         // 7.当前选择站点名称
         const stationName = ref('')
    
  7. 合理的命名: 业务相关部分,以业务组件名称命名