- 提高代码整体的
可读性、可维护性、可复用性、可移植性和可靠性
,会从根本上降低开发成本
- 保证代码的一致性,
更加易于维护
,团队内任何人都可以快速理解并修改 - 提升团队整体效率,
尽早发现问题,甚至完全预防问题
- 减少争议,
高效、高质高效完成
的目的
使用``prettier`
-
具体配置:
-
安装依赖
npm install --save-dev --save-exact prettier // or yarn add --dev --exact prettier
-
创建配置文件
echo {}> .prettierrc.json
-
创建.prettierignore文件,是其不被格式化
# Ignore artifacts: dist build coverage
-
-
全部采用小写方式, 以
-
分隔mall-management-system
-
有复数结构时,要采用复数命名法, 缩写不用复数
scripts/styles/components/images/utils/layouts/demo-styles/demo-scripts/img/doc
-
Vue项目中的目录,包括组件目录,都使用``kebab-case`命名
head-search/page-loading/authorized/notice-icon
page-one/shopping-car/user-management
-
全部采用小写方式, 以
-
分隔render-dom.js/signup.css/index.html/company-logo.png
-
严禁使用拼音与英文混合的方式,避免歧义
henan/luoyang/rmb
等国际通用的名称,可视同英
-
规定字符编码、IE 兼容模式、规定字符编码、doctype 大写
<!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)嵌套的节点应该缩进
- 每个块状元素,列表元素和表格元素后,加上 注释
-
优先使用语义化标签
<header></header> <footer></footer>
-
属性使用双引号
<div class="box"></div>
-
使用小写字母,
-
分隔 -
id 、class,scss 中的变量、函数、混合、placeholder 采用驼峰式命名
-
id 和 class 使用反应元素目的和用途的名称
.heavy { font-weight: 800; } .important { color: red; }
-
避免使用标签名
-
使用直接子选择器
.content > .title { font-size: 2rem; }
不使用直接子选择器有时候可能会很耗性能
-
避免使用 ID 及全局标签选择器,防止污染全局样式
-
每个选择器及属性独占一行
-
尽量使用缩写属性
border-top: 0; font: 100%/1.6 palatino, georgia, serif; /* font-size/line-height font-family */ padding: 0 1em 2em;
-
省略 0 后面的单位
div { padding-bottom: 0; margin: 0; }
-
将公共文件放置在``style/less/common`文件夹
-
按以下顺序组织
-
@import
-
变量声明
-
样式声明
@import "mixins/size.less"; @default-text-color: #333; .page { width: 960px; margin: 0 auto; }
-
-
避免嵌套层级过多
将嵌套深度限制在 3 ~4 级,避免出现多于 20 行的嵌套规则出现
-
采用小驼峰命名,不以
_
开头,也不以_ or $
结尾, -
方法名、参数名、成员变量、局部变量使用
lowerCamelCase
风 格,遵从驼峰形式localValue / getHttpMessage() / inputUserId
saveShopCarData /openShopCarInfoDialog
method 方法命名采用 动词 or 动词+名词 形式
命名 含义 命名 含义 命名 含义 get 获取 copy 复制 compress 压缩 set 设置 paste 粘贴 decompress 解压缩 add 增加 undo 撤销 pack 打包 remove 删除 redo 重做 unpack 解包 create 创建 insert 插入 parse 解析 destory 销毁 delete 移除 emit 生成 start 启动 add 加入 connect 连接 stop 停止 append 添加 disconnect 断开 open 打开 clean 清理 send 发送 close 关闭 clear 清除 receive 接收 read 读取 index 索引 download 下载 write 写入 sort 排序 upload 上传 load 载入 find 查找 refresh 刷新 save 保存 search 搜索 synchronize 同步 begin 开始 increase 增加 update 更新 end 结束 decrease 减少 revert 复原 backup 备份 play 播放 lock 锁定 restore 恢复 pause 暂停 unlock 解锁 import 导入 launch 启动 check out 签出 export 导出 run 运行 check in 签入 split 分割 compile 编译 submit 提交 merge 合并 execute 执行 commit 交付 inject 注入 debug 调试 push 推 extract 提取 trace 跟踪 pull 拉 attach 附着 observe 观察 expand 展开 detach 脱离 listen 监听 collapse 折叠 bind 绑定 build 构建 enter 进入 separate 分离 publish 发布 exit 退出 view 查看 input 输入 abort 放弃 browse 浏览 output 输出 quit 离开 edit 编辑 encode 编码 obsolete 废弃 modify 修改 decode 解码 depreciate 废旧 select 选取 encrypt 加密 collect 收集 mark 标记 decrypt 解密 aggregate 聚集 -
常量命名大写,单词间用
_
隔MAX_STOCK_COUNT
-
对上下文 this 的引用只能使用 ``self` 来命名。
-
使用 2 个空格缩进
-
不同逻辑、不同语义、不同业务的代码之间插入一个空行分隔开来
-
字符串统一使用单引号
''
-
对象声明使用字面值创建对象
let user = {} //不使用 new Object()
-
使用ES6+
如箭头函数、await/async , 解构, let , for…of 等等。
-
下列关键字后必须有大括号(即使代码块的内容只有一行)
if
、else
、for
、while
、do
、switch
、try
、catch
、finally
、with
-
不要直接使用 undefined 进行变量判断
if (typeof person === 'undefined') { ... }
-
条件判断和循环最多3层
三目运算符 和 逻辑运算符 代替条件判断
-
慎用
console.log
-
不直接调用
Object.prototype
,如hasOwnProperty
、propertyIsEnumerable
、isPrototypeOf
eslint: no-prototype-builtins在一些有问题的对象上,这些方法可能会被屏蔽掉,如:
{ hasOwnProperty: false }
或空对象Object.create(null)
// bad object.hasOwnProperty(key) // good Object.prototype.hasOwnProperty.call(object, key) // best const has = Object.prototype.hasOwnProperty; // 在模块作用域内做一次缓存。 has.call(object, key) /* or */ import has from 'has'; // https://www.npmjs.com/package/has has(object, key)
-
对象浅拷贝时,使用扩展运算符
...
,而不是 Object.assign。获取对象指定的属性时,用对象的 rest 解构运算符。eslint: prefer-object-spreadconst original = { a: 1, b: 2 }; // very bad const copy = Object.assign(original, { c: 3 }); // 改了 `original` ಠ_ಠ delete copy.a; // so does this // bad const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 } // good es6 扩展运算符 ... 进行浅拷贝 const copy = { ...original, c: 3 }; // copy => { a: 1, b: 2, c: 3 } // rest 解构运算符 const { a, ...rest } = copy; // rest => { b: 2, c: 3 }
-
用扩展运算符做数组浅拷贝,类似上面的对象浅拷贝
// bad const len = items.length; const itemsCopy = []; let i; for (i = 0; i < len; i += 1) { itemsCopy[i] = items[i]; } // good const itemsCopy = [...items];
-
用
...
运算符而不是 Array.from 来将一个可迭代的对象转换成数组const foo = document.querySelectorAll('.foo'); // good const nodes = Array.from(foo); // best const nodes = [...foo];
-
用 Array.from 将一个类数组对象转成一个数组
const arrLike = { 0: 'foo', 1: 'bar', 2: 'baz', length: 3 }; // bad const arr = Array.prototype.slice.call(arrLike); // good const arr = Array.from(arrLike);
-
用 Array.from 而不是
...
运算符去做 map 遍历。 因为这样可以避免创建一个临时数组// bad const baz = [...foo].map(bar); // good const baz = Array.from(foo, bar);
-
用对象的解构赋值来获取和使用对象某个或多个属性值。eslint: prefer-destructuring
解构可以避免创建临时引用和重复引用对象,避免造成代码重复、增加阅读次数、提高犯错概率
// bad function getFullName(user) { const firstName = user.firstName; const lastName = user.lastName; return `${firstName} ${lastName}`; } // good function getFullName(user) { const { firstName, lastName } = user; return `${firstName} ${lastName}`; } // best function getFullName({ firstName, lastName }) { return `${firstName} ${lastName}`; }
-
用数组解构。eslint: prefer-destructuring
const arr = [1, 2, 3, 4]; // bad const first = arr[0]; const second = arr[1]; // good const [first, second] = arr;
-
多个返回值用对象的解构,而不是数组解构\
在后期添加新的属性或者变换变量的顺序而不会破坏原有的引用
// bad function processInput(input) { // 然后就是见证奇迹的时刻 return [left, right, top, bottom]; } // 调用者需要想一想返回值的顺序 const [left, __, top] = processInput(input); // good function processInput(input) { // oops,奇迹又发生了 return { left, right, top, bottom }; } // 调用者只需要选择他想用的值就好了 const { left, top } = processInput(input);
-
字符串应使用单引号
''
。eslint: quotes -
不使用
eval()
,该方法有太多漏洞。eslint: no-eval -
不使用不必要的转义字符。eslint: no-useless-escape
反斜线可读性差,仅当必要时才使用它
-
当需要动态生成字符串时,使用模板字符串,而不是字符串拼接。eslint: prefer-template template-curly-spacing
// bad function sayHi(name) { return 'How are you, ' + name + '?'; } // bad function sayHi(name) { return ['How are you, ', name, '?'].join(); } // good function sayHi(name) { return `How are you, ${name}?`; }
-
使用函数表达式而不是函数声明。eslint: func-style
使用函数声明会发生提升(即在函数被声明之前就可以使用),使用匿名函数会导致难以追踪错误
// bad function foo() { // ... } // bad const foo = function () { // ... }; // good // lexical name distinguished from the variable-referenced invocation(s) // 函数表达式名和声明的函数名是不一样的 const short = function longUniqueMoreDescriptiveLexicalFoo() { // ... }; //简单写法 不用在意this指向 const short = name => name
-
ECMA-262 中对块(
block
)的定义是: 一系列的语句。但是函数声明不是一个语句, 函数表达式是一个语句// bad if (currentUser) { function test() { console.log('Nope.'); } } // good let test; if (currentUser) { test = () => { console.log('Yup.'); }; }
-
不使用
arguments
,用收集参数语法...
代替。eslint: prefer-rest-params// bad function concatenateAll() { const args = Array.prototype.slice.call(arguments); return args.join(''); } // good function concatenateAll(...args) { return args.join(''); }
-
用默认参数语法而不是在函数里对参数重新赋值
// really bad function handleThings(opts) { // 1. 不该修改 arguments // 2. 如果 opts 的值为 false, 它会被赋值为 {} opts = opts || {}; // ... } // still bad function handleThings(opts) { if (opts === void 0) { opts = {}; } // ... } // good function handleThings(opts = {}) { // ... }
-
不要修改参数或重新赋值 eslint: no-param-reassign
操作参数会导致意想不到的副作用。不要改参数的数据结构,保留参数原始值和数据结构
// 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) { // ... }
// bad function f1(obj) { obj.key = 1; }; // good function f2(obj) { const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1; };
-
使用拓展运算符调用多参数的函数。eslint: prefer-spread
// bad const x = [1, 2, 3, 4, 5]; console.log.apply(console, x); // good const x = [1, 2, 3, 4, 5]; console.log(...x); // bad new (Function.prototype.bind.apply(Date, [null, 2016, 8, 5])); // good new Date(...[2016, 8, 5]);
-
在回调函数里使用箭头函数。 eslint: prefer-arrow-callback, arrow-spacing
箭头函数中的
this
与定义该函数的上下文中的this
一致,语法更简洁,如果函数逻辑较复杂,则把它单独写入一个命名函数里头// 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; });
-
如果函数体由一个没有副作用的表达式语句组成,删除大括号和 return。eslint: arrow-parens, arrow-body-style
当多个函数链在一起的时候好读
// bad [1, 2, 3].map((number) => { const nextNumber = number + 1; `A string containing the ${nextNumber}.`; }); // good [1, 2, 3].map((number) => `A string containing the ${number + 1}.`); // good [1, 2, 3].map((number, index) => ({ [index]: number, })); // 没有明显的 return 语句,可能存在副作用。 function foo(callback) { const val = callback(); if (val === true) { // 当 callback 返回 true 时... } } let bool = false; // bad foo(() => bool = true); // good foo(() => { bool = true; });
-
如果表达式涉及多行,把他包裹在圆括号里以提高可读性
// bad ['get', 'post', 'put'].map((httpMethod) => Object.prototype.hasOwnProperty.call( httpMagicObjectWithAVeryLongName, httpMethod ) ); // good ['get', 'post', 'put'].map((httpMethod) => ( Object.prototype.hasOwnProperty.call( httpMagicObjectWithAVeryLongName, httpMethod ) ));
-
使用
class
语法。避免直接操作prototype
class
语法更简洁更易理解// good class Queue { constructor(contents = []) { this.queue = [...contents]; } pop() { const value = this.queue[0]; this.queue.splice(0, 1); return value; } }
-
用
extends
实现继承 -
方法可以返回
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);
-
类有默认的构造方法。一个空的构造函数或只是代表父类的构造函数是不需要写的。 eslint: no-useless-constructor
-
避免重复定义类成员。eslint: no-dupe-class-members
-
除非外部库或框架需要使用特定的非静态方法,否则类方法应该使用
this
或被写成静态方法。 作为一个实例方法表明它应该根据实例的属性有不同的行为。eslint: class-methods-use-this
-
使用(
import
/export
)模块 -
一个路径只
import
一次。eslint: no-duplicate-imports// bad import foo from 'foo'; import { named1, named2 } from 'foo'; // good import foo, { named1, named2 } from 'foo';
-
避免导出可变的东西。eslint: import/no-mutable-exports
-
在一个单一导出的模块里使用
export default
。eslint: import/prefer-default-export -
把
import
放在所有语句之前。eslint: import/first -
多行
import
应该缩进。eslint: object-curly-newline// bad import {longNameA, longNameB, longNameC, longNameD, longNameE} from 'path'; // good import { longNameA, longNameB, longNameC, longNameD, longNameE, } from 'path';
-
在
import
语句里不允许 Webpack loader 语法。eslint: import/no-webpack-loader-syntax一旦用 Webpack 语法在 import 里会把代码耦合到模块绑定器。最好是在
webpack.config.js
里写 webpack loader 语法 -
import JavaScript文件不用包含扩展名 eslint: import/extensions
-
不使用迭代器。使用 JS 高级函数代替
for-in
、for-of
。eslint: no-iterator no-restricted-syntax用数组的这些迭代方法:
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; } // good let sum = 0; numbers.forEach((num) => sum += num) // best (use the functional force) const sum = numbers.reduce((total, num) => total + num, 0) // 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);
-
暂时不使用生成器
生成器目前不能很好地转换为
ES5
语法 -
如果要用生成器,确保函数标志空格是正确。eslint: generator-star-spacing
function
和*
是同一概念关键字 -*
不是function
的修饰符,function*
是一个和function
不一样的独特结构。// good function* myGenerator() { yield 'Hello' yield 'world' return 'ending' } const MG = myGenerator() MG.next() // {value:'Hello',done:false} MG.next() // {value:'world',done:false} MG.next() // {value:'ending',done:true} MG.next() // {value:'undefined',done:false}
-
访问属性时使用点符号,当使用变量获取属性时用方括号
[]
。eslint: dot-notationconst luke = { jedi: true, age: 28, }; const isJedi = luke.jedi; function getProp(prop) { return luke[prop]; } const isJedi = getProp('jedi');
-
做幂运算时用幂操作符
**
。eslint: no-restricted-properties.// bad const binary = Math.pow(2, 10); // good const binary = 2 ** 10;
-
使用
const
或let
声明变量,并把它们分别放一起。eslint: no-undef prefer-const one-var -
变量定义在需要的地方
// good function checkName(hasName) { if (hasName === 'test') { return false; } // 在需要的时候分配 const name = getName(); if (name === 'test') { this.setName(''); return false; } return name; }
-
不使用链式声明变量。 eslint: no-multi-assign
-
不使用一元自增自减运算符(
++
,--
). eslint no-plusplus使用
num + = 1
而不是num ++
或num ++
语句也是含义清晰的,避免预料之外的预增/预减 -
在赋值的时候避免在
=
前/后换行,如果赋值语句超出 max-len,就用小括号包起来再换行。eslint operator-linebreak. -
不能有未使用的变量。eslint: no-unused-vars
- 用
===
和!==
而不是==
和!=
. eslint: eqeqeqObject
=== trueUndefined
=== falseNull
=== falseBoolean
Number
+0
,-0
,NaN
=== false- others === true
String
''
=== false- others === true
- 详见Vue笔记代码规范
-
所有命名一定要与后端命名统一,router , store, api 等都必须使用 privielege 单词
-
api 目录文件、变量命名要与后端保持一致
-
assets 为静态资源,里面存放 images, styles, icons 等静态资源,静态资源命名格式为 kebab-case
-
router 与 store 两个目录一定要将业务进行拆分,不能放到一个 js 文件里。
-
router 尽量按照 views 中的结构保持一致
-
store 按照业务进行拆分不同的 js 文件
-
-
views 目录要与后端、router、api 等保持一致
-
必须加注释的地方
- 公共组件使用说明
- api 目录的接口 js 文件
- store 中的 state, mutation, action 等
- vue 文件中的 template 必须加注释,若文件较大添加 start end 注释
- vue 文件的 methods,每个 method
- vue 文件的 data, 非常见单词要加注释
src 源码目录 |-- api 所有api接口 |-- assets 静态资源,images, icons, styles等 |-- icons |-- images | |-- background-color.png | |-- upload-header.png |-- styles |-- components 公用组件 |-- error-log | |-- index.vue | |-- index.less |-- markdown-editor | |-- index.vue | |-- index.js |-- kebab-case |-- config 配置信息 |-- constants 常量信息,项目所有Enum, 全局常量等 |-- index.js |-- role.js |-- employee.js |-- 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模块通用组件文件夹 | | | |-- role-header.vue role头部组件 | | | |-- role-modal.vue role弹出框组件 | |-- employee employee模块 | |-- behavior-log 行为日志log模块 | |-- code-generator 代码生成器模块
-
组件名由多个单词组成(大于等于 2),命名规范为
KebabCase
格式export default { name: 'TodoItem' // ... };
-
组件文件名为
pascal-case
格式
components/
|- my-component.vue
- 基础组件文件名为 base 开头,使用完整单词而不是缩写
components/
|- base-button.vue
|- base-table.vue
|- base-icon.vue
-
和父组件紧密耦合的子组件应该以父组件名作为前缀命名
components/ |- todo-list.vue |- todo-list-item.vue |- todo-list-item-button.vue |- user-profile-options.vue (完整单词)
-
在 Template 模版中使用组件,应使用
PascalCase
模式,并且使用自闭合组件。<MyComponent /> <Row><table :column="data"/></Row>
-
组件的 data 必须是一个函数
-
Prop 定义应该尽量详细
-
使用
camelCase
驼峰命名 -
指定类型
-
加上注释,表明其含义
-
加上 required 或者 default
-
有业务需要,加上 validator 验证
props: { // 组件状态,用于控制组件的颜色 status: { type: String, required: true, validator: function (value) { return [ 'succ', 'info', 'error' ].indexOf(value) !== -1 } }, // 用户级别,用于显示皇冠个数 userLevel:{ type: String, required: true } }
-
-
为组件样式设置作用域
-
特性元素较多,应该主动换行
-
模板中使用简单的表达式
复杂的表达式则应该重构为计算属性或方法
-
指令都使用缩写形式
用 : 表示 v-bind: 、用 @ 表示 v-on: 和用 # 表示 v-slot:
-
标签顺序保持一致
-
必须为 v-for 设置键值 key
-
v-show 与 v-if 选择
运行时,需要非常频繁地切换,使用 v-show ;如果在运行时,条件很少改变,使用 v-if
-
script 标签内部结构顺序
export default{ components:{ }, props:{ }, data(){ return{ } }, computed:{ }, watch:{ }, filter:{ }, //钩子函数(钩子函数按其执行顺序), methods:{ } }
-
页面跳转数据传递使用路由参数
let id = ' 123'; this.$router.push({ name: 'userCenter', query: { id: id } });
-
使用路由懒加载
{ path: '/uploadAttachment', name: 'uploadAttachment', meta: { title: '上传附件' }, component: () => import('@/view/components/uploadAttachment/index.vue') },
-
path、childrenPoints 、name命名规范采用kebab-case命名规范,和component组件名保持一致
// 动态加载 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') } ] } ];
要保持keep-alive特性,keep-alive按照component的name进行缓存,所以两者必须高度保持一致
-
path 以
/
开头{ path: '/file', name: 'File', component: Main, meta: { title: '文件服务', icon: 'ios-cloud-upload' }, children: [ { path: '/file/file-list', name: 'FileList', component: () => import('@/views/file/file-list.vue') }, { path: '/file/file-add', name: 'FileAdd', component: () => import('@/views/file/file-add.vue') }, { path: '/file/file-update', name: 'FileUpdate', component: () => import('@/views/file/file-update.vue') } ] }
- 尽量不要手动操作 DOM
- 删除无用代码