一简介
- 此规范基于脚手架vue-cli3.+
- 从编写IDE,到项目结构,命名规范,代码风格,包括代码逻辑等,都做了说明
二更新日志
1.00.20190423
- 修订人:瘾
- 编写vue项目的开发规范1.00.20190423版本(初步版本)
- 版本号规则:1 大版本记录, .00 小版本记录, .20190423 更新时间
三项目结构
3.1目录结构
├── node_modules // 依赖包
├── public // 静态资源目录不会被webpack编译
├── src
│ ├── assets // 存放公共图片,css,js
│ ├────└── images
│ ├────└── style
│ ├────└── js
│ ├── components // 非路由组件目录
│ ├────└── common // 非路由组件的全局公共组件
│ ├── router // 路由目录
│ ├────└── index.js // 路由入口,只负责入口,路由钩子,不负责具体的路由代码
│ ├────└── route // 路由文件夹,其下分模块,对路由进行管理
│ ├── service // 请求数据层文件
│ ├────└── api // 接口定义层文件,分模块去定义
│ ├────└── request.js // (axios)请求封装层,包括请求拦截
│ ├────└── env.js // 全局公共url的配置文件
│ ├── store // vuex
│ ├────└── modules // 开启命名空间,分模块处理
│ ├────└── index.js // vuex入口文件
│ ├── views // 路由组件,分模块,其模块与非路由组件中一一对应
│ ├── directive // 全局自定义指令层
│ ├── filtres // 全局自定义过滤层
│ ├── App.vue // 项目路由入口
│ ├── main.js // 入口文件
│ ├── .env.development // 开发环境变量
│ ├── .env.production // 生产环境变量
│ ├── .env.test // 测试环境变量
│ ├── .eslintrc.js // eslint配置
│ ├── .gitignore // git忽略文件
│ ├── package.json // 安装文件
│ ├── babel.config.js // babel配置
│ ├── postcss.config.js // css-loader配置
│ ├── readme.md // 项目介绍
│ ├── vue.config.js // 额外webpack配置
3.2运行编译命令
"scripts": {
"serve": "vue-cli-service serve --mode development",
"build": "vue-cli-service build --mode production",
"build:test": "vue-cli-service build --mode test",
"lint": "vue-cli-service lint"
},
环境变量配置
.env.development
NODE_ENV=development
VUE_APP_ENV=development
.env.production
NODE_ENV=production
VUE_APP_ENV=production
.env.test
NODE_ENV=production
VUE_APP_ENV=test
四代码风格
4.1代码缩进
四个空格缩进
4.2代码顺序及要求
- {{ data }}
五命名规范
5.1文件夹命名
文件夹命名采用小驼峰命名
5.2组件命名
组件名为多个单词
组件名应该始终是多个单词的,且大驼峰命名,根组件 App 除外。
正例:
export default {
name: 'TodoItem',
// ...
}
反例:
export default {
name: 'Todo',
// ...
}
单文件组件文件的大小写
单文件组件的文件名应该要始终是单词大写开头 (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
|- MySidebar.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 的
正例:
反例:
完整单词的组件名
组件名应该倾向于完整单词而不是缩写。
正例:
components/
|- StudentDashboardSettings.vue
|- UserProfileOptions.vue
反例:
components/
|- SdSettings.vue
|- UProfOpts.vue
组件文件
只要有能够拼接文件的构建系统,就把每个组件单独分成文件。
当你需要编辑一个组件或查阅一个组件的用法时,可以更快速的找到它。
正例:
components/
|- TodoList.vue
|- TodoItem.vue
反例:
Vue.component('TodoList', {
// ...
})
Vue.component('TodoItem', {
// ...
})
5.3路由命名
path使用全小写,多个单词用 - 分开,name与path保持一致
先写name,再写path,component,meta
正例:
{
name: "home-detail",
path: "/home-detail",
component: () => import("@/views/HomeDetail"),
meta: {}
}
反例:
{
name: "homeDetail",
path: "/HomeDetail",
component: () => import("@/views/HomeDetail"),
meta: {}
}
5.4请求命名
所有定义的接口以api开头小驼峰命名,尽量语义化,不要简写
正例:
export const apiHomeList = (pageInfo)=>ajax('/web/app/list.do',{pageInfo})
反例:
export const homeList = (pageInfo)=>ajax('/web/app/list.do',{pageInfo})
5.5class命名
Class 和 ID 的命名应该语义化,通过看名字就知道是干嘛的;全部小写,多个单词用连接线 - 连接
正例:
.test-header{
font-size: 20px;
}
反例:
.testheader{
font-size: 20px;
}
5.6其他命名
以上规则没有提到的统一采用小驼峰命名,语义化,不简写
六代码逻辑
6.1props定义
props 定义应该尽量详细。
在你提交的代码中,prop 的定义应该尽量详细,至少需要指定其类型。
正例:
props: {
status: String
}
// 更好的做法!
props: {
status: {
type: String,
required: true,
validator: function (value) {
return [
'syncing',
'synced',
'version-conflict',
'error'
].indexOf(value) !== -1
}
}
}
反例:
props: ['status']
6.2循环和判断
为v-for设置键值
总是用 key 配合 v-for。
在组件上总是必须用 key 配合 v-for,以便维护内部组件及其子树的状态。甚至在元素上维护可预测的行为,比如动画中的对象固化 (object constancy),也是一种好的做法。
正例:
-
{{ todo.text }}
反例:
-
{{ todo.text }}
循环
for (var i = 0; i < arr.length; i++) {} 这样的方式遍历不是很好,尤其当 arr 是 Dom 对象的时候,这样就会一直在访问 Dom 层,访问 Dom 层的代价是很大的。for (var i = 0, j=arr.length; i < j; i++) {} 这样的方式去用循环是比较好的,只会访问一次 Dom 层(不适用于 Dom 节点会动态更新的场景)。
避免 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)。
正例:
-
{{ user.name }}
反例:
-
{{ user.name }}
在 v-if/v-if-else/v-else 中使用 key
如果一组 v-if + v-else 的元素类型相同,最好使用 key (比如两个
元素)。正例:
错误:{{ error }}{{ results }}反例:
错误:{{ error }}{{ results }}6.3多个特性的元素
多个特性的元素应该分多行撰写,每个特性一行。
正例:
反例:
6.4computed
模板中简单的表达式
组件模板应该只包含简单的表达式,复杂的表达式则应该重构为计算属性或方法。
复杂表达式会让你的模板变得不那么声明式。我们应该尽量描述应该出现的是什么,而非如何计算那个值。而且计算属性和方法使得代码可以重用。正例:
{{ 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) ) } }
6.5引号
带引号的特性值
非空 HTML 特性值应该始终带引号 (单引号或双引号,优先使用 "" , JS 优先使用 '' )。
在 HTML 中不带空格的特性值是可以没有引号的,但这样做常常导致带空格的特征值被回避,导致其可读性变差。正例:
反例:
在js中
在js不再使用双引号,静态字符串使用单引号,动态字符串使用反引号衔接。
正例:
const foo = '后除' const bar = `${foo},前端工程师`
反例:
const foo = "后除" const bar = foo + ",前端工程师"
6.6指令缩写
都用指令缩写 (用 : 表示 v-bind: 和用 @ 表示 v-on:)
正例:
反例:
6.7scoped
元素选择器应该避免在 scoped 中出现。
在 scoped 样式中,类选择器比元素选择器更好,因为大量使用元素选择器是很慢的。正例:
反例:
6.8隐性的父子组件通信
应该优先通过 prop 和事件进行父子组件之间的通信,而不是 this.$parent 或改变 prop。
正例:
Vue.component('TodoItem', { props: { todo: { type: Object, required: true } }, template: ` ` })
反例:
Vue.component('TodoItem', { props: { todo: { type: Object, required: true } }, methods: { removeTodo () { var vm = this vm.$parent.todos = vm.$parent.todos.filter(function (todo) { return todo.id !== vm.todo.id }) } }, template: ` {{ todo.text }} ` })
6.9全局状态管理
应该优先通过 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) } } } {{ todo.text }}
反例:
// 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 }) } } })
6.10关于es6
6.10.1块级作用域
(1)let 取代 var
S6 提出了两个新的声明变量的命令:let和const。其中,let完全可以取代var,因为两者语义相同,而且let没有副作用,不存在变量提升。
(2)全局常量和线程安全
在let和const之间,建议优先使用const,尤其是在全局环境,不应该设置变量,只应设置常量。
- const优于let有几个原因。一个是const可以提醒阅读程序的人,这个变量不应该改变;另一个是const比较符合函数式编程思想,运算不改变值,只是新建值,而且这样也有利于将来的分布式运算;最后一个原因是 JavaScript 编译器会对const进行优化,所以多使用const,有利于提高程序的运行效率,也就是说let和const的本质区别,其实是编译器内部的处理不同。
- const声明常量还有两个好处,一是阅读代码的人立刻会意识到不应该修改这个值,二是防止了无意间修改变量值所导致的错误。
- 所有的函数都应该设置为常量。
- 长远来看,JavaScript 可能会有多线程的实现(比如 Intel 公司的 River Trail 那一类的项目),这时let表示的变量,只应出现在单线程运行的代码中,不能是多线程共享的,这样有利于保证线程安全。
6.10.2字符串
静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号。
// bad const a = "foobar"; const b = 'foo' + a + 'bar'; // acceptable const c = `foobar`; // good const a = 'foobar'; const b = `foo${a}bar`;
6.10.3解构赋值
使用数组成员对变量赋值时,优先使用解构赋值。
const arr = [1, 2, 3, 4]; // bad const first = arr[0]; const second = arr[1]; // good const [first, second] = arr;
函数的参数如果是对象的成员,优先使用解构赋值。
// 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 }) { }
如果函数返回多个值,优先使用对象的解构赋值,而不是数组的解构赋值。这样便于以后添加返回值,以及更改返回值的顺序。
// bad function processInput(input) { return [left, right, top, bottom]; } // good function processInput(input) { return { left, right, top, bottom }; } const { left, right } = processInput(input);
6.10.4对象
单行定义的对象,最后一个成员不以逗号结尾。多行定义的对象,最后一个成员以逗号结尾
// 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, };
对象尽量静态化,一旦定义,就不得随意添加新的属性。如果添加属性不可避免,要使用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;
如果对象的属性名是动态的,可以在创造对象的时候,使用属性表达式定义。
// bad const obj = { id: 5, name: 'San Francisco', }; obj[getKey('enabled')] = true; // good const obj = { id: 5, name: 'San Francisco', [getKey('enabled')]: true, };
上面代码中,对象obj的最后一个属性名,需要计算得到。这时最好采用属性表达式,在新建obj的时候,将该属性与其他属性定义在一起。这样一来,所有属性就在一个地方定义了。
另外,对象的属性和方法,尽量采用简洁表达法,这样易于描述和书写。
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; }, };
6.10.5数组
使用扩展运算符(...)拷贝数组。
// bad const len = items.length; const itemsCopy = []; let i; for (i = 0; i < len; i++) { itemsCopy[i] = items[i]; } // good const itemsCopy = [...items];
使用 Array.from 方法,将类似数组的对象转为数组。
const foo = document.querySelectorAll('.foo'); const nodes = Array.from(foo);chou
6.10.6函数
立即执行函数可以写成箭头函数的形式。
(() => { console.log('Welcome to the Internet.'); })();
那些需要使用函数表达式的场合,尽量用箭头函数代替。因为这样更简洁,而且绑定了 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);
箭头函数取代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);
简单的、单行的、不会复用的函数,建议采用箭头函数。如果函数体较为复杂,行数较多,还是应该采用传统的函数写法。
所有配置项都应该集中在一个对象,放在最后一个参数,布尔值不可以直接作为参数。// bad function divide(a, b, option = false ) { } // good function divide(a, b, { option = false } = {}) { }
不要在函数体内使用 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(''); }
使用默认值语法设置函数参数的默认值。
// bad function handleThings(opts) { opts = opts || {}; } // good function handleThings(opts = {}) { // ... }
6.10.7Map结构
注意区分 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]); }
6.10.8Class
总是用 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; } }
使用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]; } }
6.10.9模块
首先,Module 语法是 JavaScript 模块的标准写法,坚持使用这种写法。使用import取代require
// bad const moduleA = require('moduleA'); const func1 = moduleA.func1; const func2 = moduleA.func2; // good import { func1, func2 } from 'moduleA';
使用export取代module.exports。
// commonJS的写法 var React = require('react'); var Breadcrumbs = React.createClass({ render() { return ; } }); module.exports = Breadcrumbs; // ES6的写法 import React from 'react'; class Breadcrumbs extends React.Component { render() { return ; } }; export default Breadcrumbs;
如果模块只有一个输出值,就使用export default,如果模块有多个输出值,就不使用export default,export default与普通的export不要同时使用。
不要在模块输入中使用通配符。因为这样可以确保你的模块之中,有一个默认输出(export default)。
// bad import * as myObject from './importModule'; // good import myObject from './importModule';
如果模块默认输出一个函数,函数名的首字母应该小写。
function makeStyleGuide() { } export default makeStyleGuide;
如果模块默认输出一个对象,对象名的首字母应该大写。
const StyleGuide = { es6: { } }; export default StyleGuide;
七ESlint的使用
ESLint 是一个语法规则和代码风格的检查工具,可以用来保证写出语法正确、风格统一的代码。
首先,安装 ESLint。
npm i -g eslint
然后,安装 Airbnb 语法规则,以及 import、a11y、react 插件。
npm i -g eslint-config-airbnb npm i -g eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react
最后,在项目的根目录下新建一个.eslintrc文件,配置 ESLint。
{ "extends": "eslint-config-airbnb" }
现在就可以检查,当前项目的代码是否符合预设的规则。
index.js文件的代码如下。
var unusued = 'I have no purpose!'; function greet() { var message = 'Hello, World!'; alert(message); } greet();
使用 ESLint 检查这个文件,就会报出错误。
$ eslint index.js index.js 1:1 error Unexpected var, use let or const instead no-var 1:5 error unusued is defined but never used no-unused-vars 4:5 error Expected indentation of 2 characters but found 4 indent 4:5 error Unexpected var, use let or const instead no-var 5:5 error Expected indentation of 2 characters but found 4 indent ✖ 5 problems (5 errors, 0 warnings)
上面代码说明,原文件有五个错误,其中两个是不应该使用var命令,而要使用let或const;一个是定义了变量,却没有使用;另外两个是行首缩进为 4 个空格,而不是规定的 2 个空格。
八注释
JS
属性注释: // 方法注释: /** * @desc 解释 * @param {数据类型} 参数名 参数解释 */ 对象注释举例: /** * @desc 查询所有消息 * @param {Object} pageInfo 分页对象 * @property {int} pageNum 页号 * @property {int} pageSize 每页条数 */
组件
九vscode插件
Auto Rename Tag // 自动重命名配对的HTML / XML标签 Chinese (Simplified) Language Pack for Visual Studio Code // ide汉化 Color Highlight // css 颜色高亮 CSS Peek // 能够查看CSS ID和类的字符串作为HTML文件中相应的CSS定义 CSScomb // css 格式化 Debugger for Chrome // 让 vscode 映射 chrome 的 debug功能,用 vscode 来打断点调试 language-stylus // 支持.styl样式 Live Server // 项目服务端口5500 启动 Markdown All in One // 拓展md Markdown Preview Enhanced // md 预览 open in browser // 浏览器打开html px2rem // rem配置 TODO Highlight // TODO 高亮 TSLint // TS校验 TypeScript Importer // TS拓展 Vetur // vue 格式化配置 高亮 vscode-icons // 文件图标 Vue VSCode Snippets // vue 代码提示