现代软件架构的复杂性需要协同开发完成,如何高效地协同呢?无规矩不成方圆,无规范难以协同,比如,制订交通法规表面上是要限制行车权,实际上是保障公众的人身安全,试想如果没有限速,没有红绿灯,谁还敢上路行驶。对软件来说,适当的规范和标准绝不是消灭代码内容的创造性、优雅性,而是限制过度个性化,以一种普遍认可的统一方式一起做事,提升协作效率,降低沟通成本。代码的字里行间流淌的是软件系统的血液,质量的提升是尽可能少踩坑,杜绝踩重复的坑,切实提升系统稳定性,码出质量。
命名规范
项目命名
全部采用小写方式, 以中划线分隔。
// good
mall-management-system
// bad
mall_management-system / mallManagementSystem
目录命名
全部采用小写方式, 以中划线分隔,有复数结构时,要采用复数命名法, 缩写不用复数。
// good
scripts / styles / components / images / utils / layouts / demo-styles / demo-scripts / img / doc
// bad
script / style / demo_scripts / demoStyles / imgs / docs
JS、CSS、HTML、PNG 文件命名
全部采用小写方式, 以中划线分隔。
// good
render-dom.js / signup.css / index.html / company-logo.png
// bad
renderDom.js / UserManagement.html
命名严谨性
1、代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。 说明:正确的英文拼写和语法可以让阅读者易于理解,避免歧义。注意,即使纯拼音命名方式也要避免采用;
// good
henan / luoyang / rmb 等国际通用的名称,可视同英文。
// bad
DaZhePromotion [打折] / getPingfenByName() [评分] / int 某变量 = 3
2、杜绝完全不规范的缩写,避免望文不知义。
// bad
AbstractClass“缩写”命名成 AbsClass;
condition“缩写”命名成 condi,此类随意缩写严重降低了代码的可阅读性。
HTML 规范
HTML 标签
- 标签必须合法且闭合、嵌套正确,标签名需小写;
- 标签语法无错误,需要符合语义化;
- 标签的自定义属性以 data-开头,如:
<a href="#" data-num='18'></a>; - 除非有特定的功能、组件要求等,禁止随意使用 id 来定义元素样式。
链接
- 给
<a>标签加上 title 属性; <a>标签的href属性必须写上链接地址,暂无的加上javascript:alert('敬请期待!');- 非本专题的页面间跳转,采用打开新窗口模式:
target="_blank"。
flash
页面禁止使用 flash,动画使用 video、CSS3、canvas 等方式实现,低版本浏览器使用背景图片降级。
HTML 类型
推荐使用 HTML5 的文档类型申明:(建议使用 text/html 格式的 HTML。避免使用 XHTML。XHTML 以及它的属性,比如 application/xhtml+xml 在浏览器中的应用支持与优化空间都十分有限)。
规定字符编码
IE 兼容模式
规定字符编码
doctype 大写
// good
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
<meta charset="UTF-8" />
<title>Page title</title>
</head>
<body>
<img src="images/company-logo.png" alt="Company" />
</body>
</html>
缩进
- 缩进使用 2 个空格(一个Tab);
- 嵌套的节点应该缩进。
分块注释
在每一个块状元素,列表元素和表格元素后,加上一对 HTML 注释。注释格式。
语义化标签
HTML5 中新增很多语义化标签,所以优先使用语义化标签,避免一个页面都是 div 或者 p 标签。
// good
<header></header>
<footer></footer>
// bad
<div>
<p></p>
</div>
引号
使用双引号(" ") 而不是单引号('') 。
CSS 规范
命名
类名使用小写字母,以中划线分隔
id 采用驼峰式命名
scss、less 中的变量、函数、混合、placeholder 采用驼峰式命名
ID 和 class 的名称总是使用可以反应元素目的和用途的名称,或其他通用的名称,代替表象和晦涩难懂的名称。
// bad
.fw-800 {
font-weight: 800;
}
.red {
color: red;
}
// good
.heavy {
font-weight: 800;
}
.important {
color: red;
}
选择器
css 选择器中避免使用标签名,从结构、表现、行为分离的原则来看,应该尽量避免 css 中出现 HTML 标签,并且在 css 选择器中出现标签名会存在潜在的问题。
很多前端开发人员写选择器链的时候不使用 直接子选择器(注:直接子选择器和后代选择器的区别)。有时,这可能会导致疼痛的设计问题并且有时候可能会很耗性能。然而,在任何情况下,这是一个非常不好的做法。如果你不写很通用的,需要匹配到 DOM 末端的选择器, 你应该总是考虑直接子选择器。
// good
.content > .title {
font-size: 2rem;
}
// bad
.content .title {
font-size: 2rem;
}
尽量使用缩写属性
// good
border-top: 0;
font: 100%/1.6 palatino, georgia, serif;
padding: 0 1em 2em;
// bad
border-top-style: none;
font-family: palatino, georgia, serif;
font-size: 100%;
line-height: 1.6;
padding-bottom: 2em;
padding-left: 1em;
padding-right: 1em;
padding-top: 0;
每个选择器及属性独占一行
// good
button {
width:100px;
height:50px;
color:#fff;
background:#00a0e9;
}
// bad
button {
width:100px;height:50px;color:#fff;background:#00a0e9;
}
省略 0 后面的单位
// good
div {
padding-bottom: 0;
margin: 0;
}
// bad
div {
padding-bottom: 0px;
margin: 0em;
}
避免使用 ID 选择器及全局标签选择器防止污染全局样式
// good
.header {
padding-bottom: 0px;
margin: 0em;
}
// bad
#header {
padding-bottom: 0px;
margin: 0em;
}
属性书写顺序
建议遵循以下顺序:
- 布局定位属性:display / position / float / clear / visibility / overflow
- 自身属性:width / height / margin / padding / border / background
- 文本属性:color / font / text-decoration / text-align / vertical-align / white- space / break-word
- 其他属性(CSS3):content / cursor / border-radius / box-shadow / text-shadow / background:linear-gradient …
.about {
display: block;
position: relative;
float: left;
width: 100px;
height: 100px;
margin: 0 10px;
padding: 20px 0;
font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif;
color: #333;
background: rgba(0,0,0,.5);
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
-o-border-radius: 10px;
-ms-border-radius: 10px;
border-radius: 10px;
}
CSS3 浏览器私有前缀写法
CSS3 浏览器私有前缀在前,标准前缀在后。
.about {
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
-o-border-radius: 10px;
-ms-border-radius: 10px;
border-radius: 10px;
}
Javascript 规范
命名
1、采用小写驼峰命名 lowerCamelCase,代码中的命名均不能以下划线,也不能以下划线或美元符号结束;
// good
localValue / getHttpMessage() / inputUserId
// bad
_name / name_ / name$ 2) 方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格,必须遵从驼峰形式。
2、其中 method 方法命名必须是 动词 或者 动词+名词 形式;
// good
saveShopCarData /openShopCarInfoDialog
// bad
save / open / show / go
3、特此说明,增删查改,详情统一使用如下 5 个单词,不得使用其他(目的是为了统一各个端);
add / update / delete / detail / get
附: 函数方法常用的动词:
get 获取/set 设置
add 增加/remove 删除
create 创建/destory 移除
start 启动/stop 停止
open 打开/close 关闭
read 读取/write 写入
load 载入/save 保存
create 创建/destroy 销毁
begin 开始/end 结束
backup 备份/restore 恢复
import 导入/export 导出
split 分割/merge 合并
inject 注入/extract 提取
attach 附着/detach 脱离
bind 绑定/separate 分离
view 查看/browse 浏览
edit 编辑/modify 修改
select 选取/mark 标记
copy 复制/paste 粘贴
undo 撤销/redo 重做
insert 插入/delete 移除
add 加入/append 添加
clean 清理/clear 清除
index 索引/sort 排序
find 查找/search 搜索,
increase 增加/decrease 减少
play 播放/pause 暂停
launch 启动/run 运行
compile 编译/execute 执行
debug 调试/trace 跟踪
observe 观察/listen 监听
build 构建/publish 发布
input 输入/output 输出
encode 编码/decode 解码
encrypt 加密/decrypt 解密
compress 压缩/decompress 解压缩
pack 打包/unpack 解包,
parse 解析/emit 生成
connect 连接/disconnect 断开,
send 发送/receive 接收
download 下载/upload 上传
refresh 刷新/synchronize 同步
update 更新/revert 复原
lock 锁定/unlock 解锁
check out 签出/check in 签入
submit 提交/commit 交付
push 推/pull 拉
expand 展开/collapse 折叠
begin 起始/end 结束
start 开始/finish 完成
enter 进入/exit 退出
abort 放弃/quit 离开
obsolete 废弃/depreciate 废旧
collect 收集/aggregate 聚集
4、常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。
// good
MAX_STOCK_COUNT
// bad
MAX_COUNT
代码格式
1、使用 2 个空格进行缩进;
// good
if (x < y) {
x += 10;
} else {
x += 1;
}
2、不同逻辑、不同语义、不同业务的代码之间插入一个空行分隔开来以提升可读性。
说明:任何情形,没有必要插入多个空行进行隔开。
字符串
统一使用单引号(''),不使用双引号("")。这在创建 HTML 字符串非常有好处:
// good
let str = 'foo';
let testDiv = '<div id="test"></div>';
// bad
let str = 'foo';
let testDiv = "<div id='test'></div>";
对象声明
1、使用字面值创建对象;
// good
let user = {};
// bad
let user = new Object();
2、使用字面量来代替对象构造器。
// good
var user = {
age: 0,
name: 1,
city: 3
};
// bad
var user = new Object();
user.age = 0;
user.name = 0;
user.city = 0;
括号
下列关键字后必须有大括号(即使代码块的内容只有一行):
if, else, for, while, do, switch, try, catch, finally, with
// good
if (condition) {
doSomething();
}
// bad
if (condition) doSomething();
undefined 判断
永远不要直接使用 undefined 进行变量判断;使用 typeof 和字符串'undefined'对变量进行判断。
// good
if (typeof person === 'undefined') {
...
}
// bad
if (person === undefined) {
...
}
条件判断和循环最多三层
条件判断能使用三目运算符和逻辑运算符解决的,就不要使用条件判断,但是谨记不要写太长的三目运算符。如果超过 3 层请抽成函数,并写清楚注释。
this 的转换命名
对上下文 this 的引用只能使用'self'来命名
块级作用域
1、let 取代 var;
ES6 提出了两个新的声明变量的命令:let 和 const。其中,let 完全可以取代 var,因为两者语义相同,而且 let 没有副作用。
所以,建议不再使用 var 命令,而是使用 let 命令取代。
2、全局常量和线程安全。
在 let 和 const 之间,建议优先使用 const,尤其是在全局环境,不应该设置变量,只应设置常量。
const 优于 let 有几个原因。一个是 const 可以提醒阅读程序的人,这个变量不应该改变;另一个是 const 比较符合函数式编程思想,运算不改变值,只是新建值,而且这样也有利于将来的分布式运算;最后一个原因是 JavaScript 编译器会对 const 进行优化,所以多使用 const,有利于提高程序的运行效率,也就是说 let 和 const 的本质区别,其实是编译器内部的处理不同。
const 声明常量还有两个好处,一是阅读代码的人立刻会意识到不应该修改这个值,二是防止了无意间修改变量值所导致的错误。
所有的函数都应该设置为常量。
字符串
静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号。
// bad
const a = "foobar";
const b = 'foo' + a + 'bar';
// acceptable
const c = `foobar`;
// good
const a = 'foobar';
const b = `foo${a}bar`;
解构赋值
1、使用数组成员对变量赋值时,优先使用解构赋值;
const arr = [1, 2, 3, 4];
// bad
const first = arr[0];
const second = arr[1];
// good
const [first, second] = arr;
2、函数的参数如果是对象的成员,优先使用解构赋值;
// bad
function getFullName(user) {
const firstName = user.firstName;
const lastName = user.lastName;
}
// good
function getFullName(obj) {
const {
firstName,
lastName
} = obj;
}
// best
function getFullName({
firstName,
lastName
}) {}
3、如果函数返回多个值,优先使用对象的解构赋值,而不是数组的解构赋值。这样便于以后添加返回值,以及更改返回值的顺序。
// bad
function processInput(input) {
return [left, right, top, bottom];
}
// good
function processInput(input) {
return {
left,
right,
top,
bottom
};
}
const {
left,
right
} = processInput(input);
对象
1、单行定义的对象,最后一个成员不以逗号结尾。多行定义的对象,最后一个成员以逗号结尾;
// bad
const a = { k1: v1, k2: v2, };
const b = {
k1: v1,
k2: v2
};
// good
const a = { k1: v1, k2: v2 };
const b = {
k1: v1,
k2: v2,
};
2、对象尽量静态化,一旦定义,就不得随意添加新的属性。如果添加属性不可避免,要使用Object.assign方法;
// bad
const a = {};
a.x = 3;
// if reshape unavoidable
const a = {};
Object.assign(a, { x: 3 });
// good
const a = { x: null };
a.x = 3;
3、如果对象的属性名是动态的,可以在创造对象的时候,使用属性表达式定义;
// bad
const obj = {
id: 5,
name: 'San Francisco',
};
obj[getKey('enabled')] = true;
// good
const obj = {
id: 5,
name: 'San Francisco',
[getKey('enabled')]: true,
};
4、另外,对象的属性和方法,尽量采用简洁表达法,这样易于描述和书写。
var ref = 'some value';
// bad
const atom = {
ref: ref,
value: 1,
addValue: function (value) {
return atom.value + value;
},
};
// good
const atom = {
ref,
value: 1,
addValue(value) {
return atom.value + value;
},
};
数组
1、使用扩展运算符(...)拷贝数组;
// bad
const len = items.length;
const itemsCopy = [];
let i;
for (i = 0; i < len; i++) {
itemsCopy[i] = items[i];
}
// good
const itemsCopy = [...items];
2、使用 Array.from 方法,将类似数组的对象转为数组。
const foo = document.querySelectorAll('.foo');
const nodes = Array.from(foo);
函数
1、立即执行函数可以写成箭头函数的形式;
(() => {
console.log('Welcome to the Internet.');
})();
2、那些使用匿名函数当作参数的场合,尽量用箭头函数代替。因为这样更简洁,而且绑定了 this;
// bad
[1, 2, 3].map(function (x) {
return x\ * x;
});
// good
[1, 2, 3].map((x) => {
return x\ * x;
});
// best
[1, 2, 3].map(x => x\ * x);
3、箭头函数取代 Function.prototype.bind,不应再用 self/_this/that 绑定 this;
// bad
const self = this;
const boundMethod = function (...params) {
return method.apply(self, params);
}
// acceptable
const boundMethod = method.bind(this);
// best
const boundMethod = (...params) => method.apply(this, params);
4、简单的、单行的、不会复用的函数,建议采用箭头函数。如果函数体较为复杂,行数较多,还是应该采用传统的函数写法;
5、所有配置项都应该集中在一个对象,放在最后一个参数,布尔值不可以直接作为参数;
// bad
function divide(a, b, option = false) {}
// good
function divide(a, b, {
option = false
} = {}) {}
6、不要在函数体内使用 arguments 变量,使用 rest 运算符(...)代替。因为 rest 运算符显式表明你想要获取参数,而且 arguments 是一个类似数组的对象,而 rest 运算符可以提供一个真正的数组;
// bad
function concatenateAll() {
const args = Array.prototype.slice.call(arguments);
return args.join('');
}
// good
function concatenateAll(...args) {
return args.join('');
}
7、使用默认值语法设置函数参数的默认值。
// bad
function handleThings(opts) {
opts = opts || {};
}
// good
function handleThings(opts = {}) {
// ...
}
Map 解构
注意区分 Object 和 Map,只有模拟现实世界的实体对象时,才使用 Object。如果只是需要 key: value 的数据结构,使用 Map 结构。因为 Map 有内建的遍历机制。
let map = new Map(arr);
for (let key of map.keys()) {
console.log(key);
}
for (let value of map.values()) {
console.log(value);
}
for (let item of map.entries()) {
console.log(item[0], item[1]);
}
Class
1、总是用 Class,取代需要 prototype 的操作。因为 Class 的写法更简洁,更易于理解;
// 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;
}
}
2、使用 extends 实现继承,因为这样更简单,不会有破坏 instanceof 运算的危险。
// bad
const inherits = require('inherits');
function PeekableQueue(contents) {
Queue.apply(this, contents);
}
inherits(PeekableQueue, Queue);
PeekableQueue.prototype.peek = function () {
return this.\_queue[0];
}
// good
class PeekableQueue extends Queue {
peek() {
return this.\_queue[0];
}
}
模块
ES6 模块语法是 JavaScript 模块的标准写法,坚持使用这种写法,取代 Node.js 的 CommonJS 语法。
1、使用 import 取代 require();
// CommonJS 的写法
const moduleA = require('moduleA');
const func1 = moduleA.func1;
const func2 = moduleA.func2;
// ES6 的写法
import { func1, func2 } from 'moduleA';
2、使用 export 取代 module.exports;
// commonJS 的写法
var React = require('react');
var Breadcrumbs = React.createClass({
render() {
return <nav / > ;
}
});
module.exports = Breadcrumbs;
// ES6 的写法
import React from 'react';
class Breadcrumbs extends React.Component {
render() {
return <nav / > ;
}
};
export default Breadcrumbs;
如果模块只有一个输出值,就使用 export default,如果模块有多个输出值,除非其中某个输出值特别重要,否则建议不要使用 export default,即多个输出值如果是平等关系,export default 与普通的 export 就不要同时使用。
3、如果模块默认输出一个函数,函数名的首字母应该小写,表示这是一个工具方法;
function makeStyleGuide() {}
export default makeStyleGuide;
4、如果模块默认输出一个对象,对象名的首字母应该大写,表示这是一个配置值对象。
const StyleGuide = {
es6: {}
};
export default StyleGuide;
Vue 书写规范
组件名为多个单词
组件名应该始终由多个单词组成,除了根组件 App,以及 <transition>、<component> 之类的 Vue 内置组件。
这样做可以避免与现有以及未来的 HTML 元素产生冲突,因为所有的 HTML 元素名称都是单个单词的。
// good
app.component('todo-item', {
// ...
})
export default {
name: 'TodoItem',
// ...
}
// bad
app.component('todo', {
// ...
})
export default {
name: 'TodoItem',
// ...
}
Prop定义
Prop 定义应尽量详细
在提交的代码中,prop 的定义应该尽量详细,至少指定其类型。
细致的 prop 定义有两个优势:
- 它们写明了组件的 API,所以组件的设计用法可以通俗易懂;
- 在开发环境下,如果为一个组件提供了格式不正确的 prop,Vue 将会告警,以帮助你捕获潜在的错误来源。
// good
props: {
status: String
}
// 更好的例子
props: {
status: {
type: String,
required: true,
validator: value => {
return [
'syncing',
'synced',
'version-conflict',
'error'
].includes(value)
}
}
}
// bad
// 只有在原型开发时,这么做才能被接受
props: ['status']
为 v-for 设置 key 值
始终以 key 配合 v-for。
在组件上必须始终以 key 配合 v-for,以便维护内部组件及其子树的状态。即使对于元素,维持可预测的行为也是一种好的做法。例如动画中的对象固化 (object constancy) 。
// good
<ul>
<li
v-for="todo in todos"
:key="todo.id"
>
{{ todo.text }}
</li>
</ul>
// bad
<ul>
<li v-for="todo in todos">
{{ todo.text }}
</li>
</ul>
避免 v-if 和 v-for 一起使用
永远不要在一个元素上同时使用 v-if 和 v-for。
一般我们在两种常见的情况下会倾向于这样做:
- 为了对列表中的项目进行过滤 (比如
v-for="user in users" v-if="user.isActive")。在这种情形下,请将users替换为一个计算属性 (比如activeUsers),返回过滤后的列表。 - 为了避免渲染本应该被隐藏的列表 (比如
v-for="user in users" v-if="shouldShowUsers")。这种情形下,请将v-if移动至容器元素上 (比如ul、ol)。
// good
<ul>
<template v-for="user in users" :key="user.id">
<li v-if="user.isActive">
{{ user.name }}
</li>
</template>
</ul>
// bad
<ul>
<li
v-for="user in users"
v-if="user.isActive"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
为组件样式设置作用域
对于应用来说,样式在顶层 App 组件和布局组件中可以是全局的,但是在其它所有组件中都应该是有作用域的。
这条规则只适用于单文件组件。你不一定要使用 scoped attribute。作用域也可以通过 CSS Modules (一个基于 class 的,类似 BEM 的策略) 或者其它的库/约定来实现。
不管怎样,对于组件库来说,我们应该更倾向于选用基于 class 的策略,而不是 scoped attribute。
这会让覆写内部样式变得更容易:使用人类可理解的 class 名称,没有太高的选择器优先级,而且不太会导致冲突。
// good
<template>
<button class="button button-close">×</button>
</template>
<!-- 使用 `scoped` attribute -->
<style scoped>
.button {
border: none;
border-radius: 2px;
}
.button-close {
background-color: red;
}
</style>
// bad
<template>
<button class="btn btn-close">×</button>
</template>
<style>
.btn-close {
background-color: red;
}
</style>
私有 property 名称
使用模块作用域来确保外部无法访问到私有函数。如果无法做到这一点,就始终应该为插件、mixin 等不考虑对外公开 API 的自定义私有 property 使用 $_ 前缀。并附带一个命名空间,以回避和其它作者的冲突 (比如 $_yourPluginName_)。
-
Vue 使用
_前缀来定义其自身的私有 property,所以使用相同的前缀 (比如_update) 有覆写实例 property 的风险。即便你检查确认了 Vue 当前版本没有用到这个 property 名,也不能保证和将来的版本没有冲突。 -
对于
$前缀来说,其在 Vue 生态系统中的目的是暴露给用户的一个特殊的实例 property,所以把它用于私有 property 并不合适。 -
不过,我们推荐把这两个前缀结合为
$_,作为一个用户定义的私有 property 的约定,以确保不会和 Vue 自身相冲突。
// Even better!
const myGreatMixin = {
// ...
methods: {
publicMethod() {
// ...
myPrivateFunction()
}
}
}
function myPrivateFunction() {
// ...
}
export default myGreatMixin
// good
const myGreatMixin = {
// ...
methods: {
$_myGreatMixin_update() {
// ...
}
}
}
// bad
const myGreatMixin = {
// ...
methods: {
update() {
// ...
}
}
}
const myGreatMixin = {
// ...
methods: {
_update() {
// ...
}
}
}
const myGreatMixin = {
// ...
methods: {
$update() {
// ...
}
}
}
const myGreatMixin = {
// ...
methods: {
$_update() {
// ...
}
}
}
组件文件
只要有能够拼接文件的构建系统,就把每个组件单独分成文件。
当你需要编辑一个组件,或查阅一个组件的用法时,这种做法可以帮助你更快速地找到它。
// good
components/
|- TodoList.js
|- TodoItem.js
components/
|- TodoList.vue
|- TodoItem.vue
// bad
app.component('TodoList', {
// ...
})
app.component('TodoItem', {
// ...
})
单文件组件文件的大小写
单文件组件的文件名应该要么始终是单词大写开头 (PascalCase),要么始终是横线连接 (kebab-case)。建议使用kebab-case格式。
// good
components/
|- my-component.vue
// bad
components/
|- mycomponent.vue
基础组件名称
应用特定样式和约定的基础组件 (也就是展示类的、无逻辑的或无状态的组件) 应该全部以一个特定的前缀开头,比如 Base、App 或 V。
// good
components/
|- BaseButton.vue
|- BaseTable.vue
|- BaseIcon.vue
components/
|- AppButton.vue
|- AppTable.vue
|- AppIcon.vue
components/
|- VButton.vue
|- VTable.vue
|- VIcon.vue
// bad
components/
|- MyButton.vue
|- VueTable.vue
|- Icon.vue
紧密耦合的组件名称
与父组件紧密耦合的子组件应该以父组件名作为前缀命名。
// good
components/
|- todo-list.vue
|- todo-list-item.vue
|- todo-list-item-button.vue
// bad
components/
|- TodoList.vue
|- TodoItem.vue
|- TodoButton.vue
如果特性元素较多,应该主动换行
// good
<MyComponent
foo="a"
bar="b"
baz="c"
foo="a"
bar="b"
baz="c"
foo="a"
bar="b"
baz="c"
/>
// bad
<MyComponent foo="a" bar="b" baz="c" foo="a" bar="b" baz="c" foo="a" bar="b" baz="c" foo="a" bar="b" baz="c"/>
模板中使用简单的表达式
组件模板应该只包含简单的表达式,复杂的表达式则应该重构为计算属性或方法。
// good
<template>
<p>{{ normalizedFullName }}</p>
</template>
// 复杂表达式已经移入一个计算属性
computed: {
normalizedFullName: function () {
return this.fullName.split(' ').map(function (word) {
return word[0].toUpperCase() + word.slice(1)
}).join(' ')
}
}
// bad
<template>
<p>
{{ fullName.split(' ').map(function (word) { return word[0].toUpperCase() +
word.slice(1) }).join(' ') }}
</p>
</template>
指令缩写
指令缩写 (用 : 表示 v-bind:,@ 表示 v-on: 和用 # 表示 v-slot) 应该要么始终使用,要么始终不使用。
// good
<input
:value="newTodoText"
:placeholder="newTodoInstructions"
>
<input
v-bind:value="newTodoText"
v-bind:placeholder="newTodoInstructions"
>
<input
@input="onInput"
@focus="onFocus"
>
<input
v-on:input="onInput"
v-on:focus="onFocus"
>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
<template #header>
<h1>Here might be a page title</h1>
</template>
<template #footer>
<p>Here's some contact info</p>
</template>
// bad
<input
v-bind:value="newTodoText"
:placeholder="newTodoInstructions"
>
<input
v-on:input="onInput"
@focus="onFocus"
>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<template #footer>
<p>Here's some contact info</p>
</template>
标签顺序保持一致
单文件组件应该总是让标签顺序保持为
// good
<template>...</template>
<script>...</script>
<style>...</style>
// bad
<template>...</template>
<style>...</style>
<script>...</script>
v-show 与 v-if 选择
如果运行时,需要非常频繁地切换,使用 v-show ;如果在运行时,条件很少改变,使用 v-if。
组件/实例选项的顺序
组件/实例的选项应该有统一的顺序。
这是我们为组件选项推荐的默认顺序。它们被划分为几大类,你能够由此知道新的 property 应该被放到哪里。
-
全局感知 (要求在组件以外被感知)
name
-
模板编译选项 (改变模板编译的方式)
compilerOptions
-
模板依赖 (模板内使用的资源)
componentsdirectives
-
组合 (合并 property 至选项内)
extendsmixinsprovide/inject
-
接口 (组件的接口)
inheritAttrspropsemitsexpose
-
组合式 API (使用组合式 API 的入口点)
setup
-
本地状态 (本地的响应式 property)
datacomputed
-
事件 (通过响应式事件触发的回调)
-
watch -
生命周期事件 (按照它们被调用的顺序)
beforeCreatecreatedbeforeMountmountedbeforeUpdateupdatedactivateddeactivatedbeforeUnmountunmountederrorCapturedrenderTrackedrenderTriggered
-
-
非响应式的 property (不依赖响应性系统的实例 property)
methods
-
渲染 (组件输出的声明式描述)
template/render
元素 attribute 的顺序
元素 (包括组件) 的 attribute 应该有统一的顺序。
这是我们为组件选项推荐的默认顺序。它们被划分为几大类,你能够由此知道新添加的自定义 attribute 和指令应该被放到哪里。
-
定义 (提供组件的选项)
is
-
列表渲染 (创建相同元素的多个变体)
v-for
-
条件 (元素是否渲染/显示)
v-ifv-else-ifv-elsev-showv-cloak
-
渲染修饰符 (改变元素的渲染方式)
v-prev-once
-
全局感知 (要求在组件以外被感知)
id
-
唯一性 Attribute (需要唯一值的 attribute)
refkey
-
双向绑定 (结合了绑定与事件)
v-model
-
其他 Attribute (所有普通的、绑定或未绑定的 attribute)
-
事件 (组件事件监听器)
v-on
-
内容 (覆写元素的内容)
v-htmlv-text
scoped 中的元素选择器
元素选择器应该避免在 scoped 中出现。
在 scoped 样式中,类选择器要比元素选择器更好,因为大量地使用元素选择器是很慢的。
// good
<template>
<button class="btn btn-close">×</button>
</template>
<style scoped>
.btn-close {
background-color: red;
}
</style>
// bad
<template>
<button>×</button>
</template>
<style scoped>
button {
background-color: red;
}
</style>
Vue Router 规范
页面跳转数据传递使用路由参数
// good
let id = '123';
this.$router.push({
name: 'userCenter',
query: {
id: id
}
});
使用路由懒加载(延迟加载)机制
// good
{
path: '/uploadAttachment',
name: 'uploadAttachment',
meta: {
title: '上传附件'
},
component: () => import('@/view/components/uploadAttachment/index.vue')
},
router 中的命名规范
path、childrenPoints 命名规范采用 kebab-case 命名规范(尽量 vue 文件的目录结构保持一致,因为目录、文件名都是 kebab-case,这样很方便找到对应的文件)
**name 命名规范采用 KebabCase 命名规范且和 component 组件名保持一致!(因为要保持 keep-alive 特性,keep-alive 按照 component 的 name 进行缓存,所以两者必须高度保持一致)
// 动态加载
export const reload = [{
path: '/reload',
name: 'reload',
component: Main,
meta: {
title: '动态加载',
icon: 'icon iconfont'
},
children: [{
path: '/reload/smart-reload-list',
name: 'SmartReloadList',
meta: {
title: 'SmartReload',
childrenPoints: [{
title: '查询',
name: 'smart-reload-search'
},
{
title: '执行reload',
name: 'smart-reload-update'
},
{
title: '查看执行结果',
name: 'smart-reload-result'
}
]
},
component: () =>
import('@/views/reload/smart-reload/smart-reload-list.vue')
}]
}
];
Vue 项目目录规范
基础
vue 项目中的所有命名一定要与后端命名统一。比如权限:后端 privilege, 前端无论 router , store, api 等都必须使用 privielege 单词!
目录说明
src 源码目录
|-- api 所有 api 接口
|-- assets 静态资源,images, icons, styles 等
|-- components 公用组件
|-- config 配置信息
|-- constants 常量信息,项目所有 Enum, 全局常量等
|-- directives 自定义指令
|-- filters 过滤器,全局工具
|-- datas 模拟数据,临时存放
|-- lib 外部引用的插件存放及修改文件
|-- mock 模拟接口,临时存放
|-- plugins 插件,全局使用
|-- router 路由,统一管理
|-- store vuex, 统一管理
|-- themes 自定义样式主题
|-- views 视图目录
| |-- role role 模块名
| |-- |-- role-list.vue role 列表页面
| |-- |-- role-add.vue role 新建页面
| |-- |-- role-update.vue role 更新页面
| |-- |-- index.less role 模块样式
| |-- |-- components role 模块通用组件文件夹
| |-- employee employee 模块
api 目录
文件、变量命名要与后端保持一致。
此目录对应后端 API 接口,按照后端一个 controller 一个 api js 文件。若项目较大时,可以按照业务划分子目录,并与后端保持一致。
api 中的方法名字要与后端 api url 尽量保持语义高度一致性。
对于 api 中的每个方法要添加注释,注释与后端 swagger 文档保持一致。
后端:
url: EmployeeController.java
/employee/add
/employee/delete/{id}
/employee/update
前端:
employee.js
// 添加员工
addEmployee: (data) => {
return postAxios('/employee/add', data)
},
// 更新员工信息
updateEmployee: (data) => {
return postAxios('/employee/update', data)
},
// 删除员工
deleteEmployee: (employeeId) => {
return postAxios('/employee/delete/' + employeeId)
},
assets 目录
assets 为静态资源,里面存放 images, styles, icons 等静态资源,静态资源命名格式为 kebab-case
|assets
|-- icons
|-- images
| |-- background-color.png
| |-- upload-header.png
|-- styles
components 目录
此目录应按照组件进行目录划分,目录命名为 KebabCase,组件命名规则也为 KebabCase
|components
|-- error-log
| |-- index.vue
| |-- index.less
|-- markdown-editor
| |-- index.vue
| |-- index.js
|-- kebab-case
constants 目录
此目录存放项目所有常量,如果常量在 vue 中使用,请使用 vue-enum 插件(https://www.npmjs.com/package/vue-enum)
目录结构:
|constants
|-- index.js
|-- role.js
|-- employee.js
例子: employee.js
export const EMPLOYEE_STATUS = {
NORMAL: {
value: 1,
desc: '正常'
},
DISABLED: {
value: 1,
desc: '禁用'
},
DELETED: {
value: 2,
desc: '已删除'
}
};
export const EMPLOYEE_ACCOUNT_TYPE = {
QQ: {
value: 1,
desc: 'QQ 登录'
},
WECHAT: {
value: 2,
desc: '微信登录'
},
DINGDING: {
value: 3,
desc: '钉钉登录'
},
USERNAME: {
value: 4,
desc: '用户名密码登录'
}
};
export default {
EMPLOYEE_STATUS,
EMPLOYEE_ACCOUNT_TYPE
};
router 与 store 目录
这两个目录一定要将业务进行拆分,不能放到一个 js 文件里。
router 尽量按照 views 中的结构保持一致。
store 按照业务进行拆分不同的 js 文件。
views 目录
命名要与后端、router、api 等保持一致
components 中组件要使用 PascalCase 规则
|-- views 视图目录
| |-- role role 模块名
| | |-- role-list.vue role 列表页面
| | |-- role-add.vue role 新建页面
| | |-- role-update.vue role 更新页面
| | |-- index.less role 模块样式
| | |-- components role 模块通用组件文件夹
| | | |-- role-header.vue role 头部组件
| | | |-- role-modal.vue role 弹出框组件
| |-- employee employee 模块
| |-- behavior-log 行为日志 log 模块
| |-- code-generator 代码生成器模块
注释说明
整理必须加注释的地方
公共组件使用说明
api 目录的接口 js 文件必须加注释
store 中的 state, mutation, action 等必须加注释
vue 文件中的 template 必须加注释,若文件较大添加 start end 注释
vue 文件的 methods,每个 method 必须添加注释
vue 文件的 data, 非常见单词要加注释
其他
尽量不要手动操作 DOM
因使用 vue 框架,所以在项目开发中尽量使用 vue 的数据驱动更新 DOM,尽量(不到万不得已)不要手动操作 DOM,包括:增删改 dom 元素、以及更改样式、添加事件等。
删除无用代码
因使用了 git/svn 等代码版本工具,对于无用代码必须及时删除,例如:一些调试的 console 语句、无用的弃用功能代码。