[Vue进阶]为什么我的代码让别人看起来头皮发麻?
前面的话
首先我想说的是,这篇文章不是介绍什么高深的技术,请各位熟知。涉及的都是日常开发当中一些不符合规范的案例,借此分享给诸位。如果你是小白,或许这篇文章对你有点帮助,如果你是老司机,看完请轻点拍!有什么建议或意见请留言斧正,谢谢!
有些同学在开发某个新功能时根据需求就哐哐哐(按照自己的代码风格)一顿撸。写完发现,另一个地方也有这个模块功能,可能只是标题的颜色,字体大小不对。怎么办? 于是很鸡贼的复制粘贴过去,改吧改吧,提交代码,万事大吉!自己倒是爽了,功能是按照需求如期完成了啊,没毛病。可是你却忽视了一件很重要的东西:团队。
记住,你不是一个人在写代码。
这篇文章有别于其他教程类的文章,不是教你如何制定代码规范,也不是告诉你这样写就是错的亦或说是正确的。本文是我这些天从优化别人代码过程中的所见和所得,凝结成文。旨在分享给大家,对号入座,然后改之。三人行,必有我师!;择其善者而从之,其不善者而改之。 由于我是做前端的,所以只说前端代码规范,其他语言同样适用!
目的
把一些常见的错误的不良的代码示例分享给大家,希望有的改之,无则加勉。看完之后,希望对你们有所帮助,提高自己的代码质量,每个人都能写出一手漂亮的代码。这是这篇文章最大的目的了!
概述
本文将以我的亲身项目经历为例,来谈谈我们日常开发当中,就代码层面来讲,我们应该注意的一些小细节。希望各位看客能吸取精华去其糟粕。主要涉及的方面有:
项目结构
文件命名
路由
Vue 组件
JavaScript
Html
Css
Git 代码提交
我将会从以上几个方面逐一枚举和大家分享讨论。
枚举
1. 项目结构
没说之前,您不妨看下自己的项目结构是什么样的。目前我们的项目结构是这样的:
my-project ├── .idea # 这个是编辑器生成的 ├── build # Webpack 配置文件放在这里 ├── config # Vue 基本配置文件放在这里 ├── node_modules # 第三方依赖 ├── src # 项目源码(核心文件) │ ├── assets # 资源文件(js, css, scss) │ ├── components # 所有组件 │ ├── js # 自己写的 js,里面各种工具类方法等 │ ├── mixins # 混合 │ ├── router # 路由 │ ├── vuex # 状态管理 │ ├── App.vue # 根组件 │ └── main.js # 入口文件 ├── static # 静态资源,一般放 img ├── theme # 主题文件,修改的 Element-UI 主题 ├── .babelrc # babel 编译配置 ├── .editorconfig # 代码格式 ├── .gitignore # Git 提交忽略的文件配置 ├── .postcssrc.js # 转换 css 的工具配置文件 ├── element-variables.css # Element 全局定义的变量,不明白为啥放这儿 ├── index.html # 主页模板 ├── package-lock.json # 用来锁定依赖的版本号(NPM 自动生成) ├── package.json # 项目基本信息 └── README.md # 项目介绍 复制代码
都是用 vue-cli 生成的,目录结构和命名规范也就没啥可说的。可能随着时间的推移,自己会在项目里加一些东西(文件或文件夹)。拿上面我们的项目为例来说几点吧:
根目录下不要有 css 文件:
比如 element-variables.css
文件,虽然这个文件是 Element自定义主题 自动生产的,但是可以通过配置更换生成所在目录。因为它属于 theme
文件夹下的东西,所以应该放它下面的。
js
文件夹应该命名为utils
:
因为它对外暴露的都是工具类方法,这样更显语义化。
关于项目结构,我发现的就这么多。每个项目的目录可能会不同,这个就看你们的规范了。
2. 文件的命名
它包含文件的命名和文件夹的命名。依我们的项目为例,我重点说下 src/components
目录下的命名,真的是五花八门:
2.1. 文件名不够语义化
这个还算正常,但还是有些问题。这里是一些问题清单:
这个模块的中文叫,是关于机器人学习的,叫
knowledgeBaseManagement
虽然很好的翻译了中文意思,但总觉的有点长,叫robot
会不会好些?而且文件夹下的文件命名也不够语义化
下面是整理过后的样子:
robot ├── addQuestion.vue ├── editQuestion.vue ├── index.vue └── missedQuestion.vue 复制代码
这个我就不想说了,看的我头皮发麻。从字面意思上来讲,我就认识一个 TreeNode2.vue
。后面还加个 2 是什么鬼?
2.2. 文件目录不统一
这属于一类问题,即里面太乱了,不统一,问题清单:
src/components/moduleName/
下除了子模块外,尽量不要瞎放其他无关的文件夹,如上面的src
、component/common
、top
callcenterList/src
下的图片可以放到static
下如果是功能型组件(上面的
color
是一个颜色选择器组件),尽量放到一个叫package
或者lib
的文件夹下。因为src/components
下的模块都是系统模块,不要混淆。elvesSetting/top
如果是某几个页面头部的公共部分尽量放到components/common
下
2.3. 文件名过长
如果一个模块下就一个文件,尽量写成 index.vue
。这里文件夹和文件同名,路由是不是很长?你在其他文件中 import
的时候是不是也不方便? 而且我发现 problemManagement
和 problemRetrieve
都属于问题管理模块,完全可以合并到一个文件夹里啊。还有,文件夹已经表明是问题管理模块了,所以文件名就不要再以 problem***
开头了。不觉得啰嗦吗? 下面是整理过后的样子:
problemManagement │ ├── index.vue │ ├── retrieve.vue qualityCheckAppeal └── index.vue 复制代码
3. 路由
我们系统里的路由都是一级路由。举个栗子:
userManagement ├── add.vue └── update.vue 复制代码
用户管理下有增改两个功能,不使用弹框去做的前提下,假如说 add
和 update
对应两个路由是 /addUser
,/updateUser
。我们系统地址栏是这样显示的:
// 增加用户 localhost:3030/addUser // 修改用户 localhost:3030/updateUser?id=1 复制代码
虽然地址栏路由短看起来会让人舒服,但是模块多的话,就不容易区分,其实应该这样做:
// 增加用户 localhost:3030/user/add // 修改用户 localhost:3030/user/update?id=1 ... // 总结 localhost:3030/module/function?queryString 复制代码
当然也可以使用最近流行的 RESTful API 设置规范,专门用于 Web 数据结构的设置。阮一峰老师有一篇非常不错的文章,推荐给大家,我就不再赘述辣。传送门->
4. Vue 组件
关于 Vue 组件开发规范可以参考官方的风格指南。下面是我们项目的一些问题清单和改正意见,我列举一下作为对照:
不要在
App.vue
中直接修改第三方样式(比如:ElementUI
)。请使用外部文件导入:
App.vue
文件:
<!-- incorrect --> ... <style> .el-input__icon { cursor: pointer } </style> <!-- correct --> ... <style> @import 'element-style-overwrite'; ... </style> 复制代码
_element-style-overwrite.scss
外部样式文件:
.el-input__icon { cursor: pointer } 复制代码
给每个组件起个名字是个好习惯。例如
Dialog
组件:
// incorrect export default { ... } // correct export default { name: 'MyDialog', // 以大驼峰命名 ... } 复制代码
给组件样式设置作用域
scoped
如果你在某个子组件中修改了全局样式,本来只想在该组件中使用,没想到造成了全局污染。等进行代码 review 的时候是很难排查的。
例如,用户管理(UserManagement.vue
)组件:
<style scoped> ... </style> 复制代码
组件名要么单词大写开头 (PascalCase),要么横线连接(kebab-case):
// incorrect components/ └── mycomponent.vue components/ └── myComponent.vue // correct components/ └── MyComponent.vue // 或者 components/ └── my-component.vue 复制代码
.vue 单文件中的
<template>
、<script>
、<style>
标签的顺序问题
有的人喜欢这样写:
<style>...</style> <template>...</template> <script>...</script> 复制代码
也有人喜欢这样写:
<script>...</script> <style>...</style> <template>...</template> 复制代码
如果你想写,那好,不阻拦,拜托你统一下行不?别这个组件这个顺序,那个组件那个顺序。累不累? 这里我强力推荐大家按照官方的写法,即下面的顺序来写:
<template>...</template> <script>...</script> <style scoped>...</style> 复制代码
组件中的字体图标(icon)不要用 png 图片
不知道你们项目里有没有很多 icon 图标。反正我们项目不少且都是 png 图片。静态文件夹里好多小图标。本来左侧菜单也都是 png 图标的,被我看着不爽重构了一下。把所有的 png 图标换成了 fontIcon 字体。
字体图标的优势:
减少 http 请求和项目体积
样式容易控制
用户体验好
如何制作 fontIcon 字体图标呢?其实很简单:
1、可以先去阿里图库找自己喜欢的或者让你自己家的UI小姐姐做。
2、下载 svg 格式的,如果是UI做的,记得让她转换下。
3、去 icomoon 字体图标生成网站导入刚才所有的 svg 图标,设置字体名称导出即可。
4、再在文件中引用,大功告成。
使用两个空格(space)进行缩进
这个放在全局规范会比较好一些。为什么是两个空格? 大神们都是这样做的!而且更重要的是,使用两个空格开发项目,传到 github 或者 gitlab 上排版会很好看。什么?不会设置?百度啊!你用的什么编辑器就查这个编辑器怎么设置的。
一般是统一把全局规范设置放到一个叫 .editorconfig 的文件夹里,有的编辑器支持这个文件,比如:webstorm
。有的则不支持,对于不支持的编辑器,可以下载安装 editorConfig
插件,如:atom
、sublime
、vscode
等。
代码中不用的注释都删掉
调试结束,把不用的
console.log(...)
及时删掉,它会影响性能data
中的属性命名和初始化问题
// incorrect export default { data () { return { text: 'wwwwwwww', // 这是啥? editBoxId: null, // 很明显Id是String,这里他初始化一个 null flag: '', // 这个表示的啥?看意思应该是个 Boolean 类型,为啥弄个 String ? pSize: 10, // pSize 是啥? cPage: 1, // cPage 是啥? popCsr:true, // popCsr 是啥,恐怕现在连那个开发者自己都不知道了吧 callcenterAuthority: false, // 这么长你告诉是一个 Boolean 类型的 } } } // correct export default { data () { return { text: '', // 'wwwwwwww' 没卵用删掉 editBoxId: -1, // 它应该是个 Number 类型 flag: false, // 它应该是个 Boolean 类型啊 pageSize: 10, // pSize -> pageSize 多好 currentPage: 1, // 完整写法更易懂,不是吗? isPopcsr: true, // Boolean 类型的总是前面加个 is isAuthority: false, // 是否授权。 } } } 复制代码
其实还有好多问题,我就不一一列举了。诸如此类的问题,希望各位看客们都能吸取精华,去其糟粕。
Props
中的属性声明要明确类型
// incorrect export default { props: ['node', 'size'] } // correct export default { props: { node: Object, // 对象 size: [String, Number], // 两种类型都可以 } } 复制代码
Vue 生命周期函数按顺序放在
methods
之前
为什么说这个呢? 我们项目中有的组件就 methods
中的代码就上千行。如果生命周期函数放在 methods
之后,拉来拉去非常不方便:
// incorrect export default { ... created () {}, methods: { // 省略 1000 行代码 // ... }, mounted () {}, beforeDestroy () {}, destroy () {}, } // correct export default { ... created () {}, mounted () {}, beforeDestroy () {}, destroy () {}, methods: { // 省略 1000 行代码 // ... } } 复制代码
Vue 组件中的
this
赋值要统一
代码中,有时候我们需要把 this
赋给一个变量,你要么统一赋值给变量 vm
,要么统一赋值给变量 self
。别一个组件里,变来变去。
// incorrect export default { ... methods: { one () { let vm = this }, two () { let self = this } } } // incorrect export default { ... methods: { one () { let vm = this // 或者 let self = this }, two () { let vm = this // 或者 let self = this } } } 复制代码
Vue 组件中 Html 如果过长,请换行
<!-- incorrect --> <el-input v-model="ruleForm.maskInput" size="small" class="nodeIpt" :icon="ruleForm.maskInput ? 'circle-close':''" @click="ruleForm.maskInput = ''" @keyup.enter.native="nodesure($event,'ruleForm')"></el-input> <!-- correct --> <el-input v-model="ruleForm.maskInput" size="small" class="nodeIpt" :icon="ruleForm.maskInput ? 'circle-close':''" @click="ruleForm.maskInput = ''" @keyup.enter.native="nodesure($event,'ruleForm')"> </el-input> 复制代码
Vue 中监听的事件记得垃圾回收
举个例子,如果我们在 Vue 组件的 created
声明周期钩子中监听了一个点击事件,那么,当组件销毁(beforeDestroy)之前记得把这个事件释放,看代码:
export default { ... created () { document.addEventListener('click', this.handleClick) }, beforeDestroy () { document.removeEventListener('click', this.handleClick) } } 复制代码
Vue 组件中不要直接操作异步请求(axios)
把所有的异步请求方法封装成一个独立 js 文件,或者放到 Vuex 中,千万不要耦合到 Vue 组件中。因为代码量太多,会加重组件的后期维护,各司其职不好吗?
不好的范例:
// User.vue export default { ... mounted () { this.getUsers() }, methods: { getUsers () { this.axios(url, data, (response) => { // Do something }).catch(err => { console.error(err) }) } } } 复制代码
如果项目比较小还好,我没意见,如果项目较复杂,千万别这么干。下面是推荐的做法:
// server.js // 专门处理数据请求的文件,也就是我没常说的MVC中的 M 层 import axios from 'axios' export default { /** * 获取用户列表 */ getUsers (url, data) { return axios.get(url, data) } } // User.vue import api from '@/api/server.js' export default { ... data () { return { users: null } }, mounted () { api.getUsers((response) => { this.users = response.data.data }).catch(err => { console.log(err) }) } } 复制代码
5. JavaScript
下面所有的错误代码示例都是从我们的项目中发现的,捡主要的列出来一些。希望犯同样错误的你能及时改正哦~
变量命名
要语义化命名
// incorrect var a = document.getElementById(this.lastid) // 这里的 a var aa = true // 这是啥你们知道吗? // corrent let orderId = this.order.id let currentTime = Date.now() 复制代码
多个单词要驼峰命名
// incorrent vm.timedefault = timedvalue vm.currentsessionid = id // corrent vm.timeDefault = timedValue vm.currentSessionId = id 复制代码
变量要加注释
上面那一坨你们知道啥意思吗?如果这个开发人员离职了,那可是坑了后来人了。所以,做开发不能自己爽了,做一个帅气和代码于一身的工程师,难道不更好吗?
不要重复使用 var 声明变量
// incorrect var name = 'test'; var age = 12; var hobby = 'sport'; // correct var name = 'test', age = 12, hobby = 'sport'; 复制代码
=
或==
之间要保留一个空格
错误的范例:
// 变量 var name='test' var arr=[] var obj={ id:1 } // if 判断 if(this.id==currentId){ // Do something } // for 循环 for(let i=0;i<arr.length;i++){ // Do something } 复制代码
上面三种情况是最常见的,其他雷同。下面是正确的范例:
// 变量 var name = 'test' var arr = [] var obj = { id: 1 } // if 判断 if(this.id == currentId) { // Do something } // for 循环 for(let i = 0; i < arr.length; i++) { // Do something } 复制代码
右括号
)
遇到 左大括号{
时要空一格
下面是错误的范例:
// if if(a === b){ // Do something } // for for(let i = 0; i < arr.length; i++){...} // 函数 var T = function(params){ ... } 复制代码
常见的几种情况,其他情况不再列举。下面是正确的范例:
// if if (a === b) { // Do something } // for for (let i = 0; i < arr.length; i++) {...} // 函数 var T = function(params) { ... } 复制代码
非空判断问题
在我们项目里,有人这样写:
// 假如 Vue 组件中有一个叫 userId 的 data 属性 if (userId != '' || userId != 0 || userId != false || userId != null || userId != undefined) { // ... } 复制代码
当遇到上面几种情况的时候,下面代码实现的效果是一样的:
if (!userId) { // ... } 复制代码
对象声明问题
不要用下面的方式之一去声明一个对象:
// incorrect var arr = new Array() // 数组 var arr = '' // 虽然 js 是弱类型,也不能这样声明 var obj = new object() // 对象 var obj = '' 复制代码
下面是推荐做法,也是大众做法:
// 声明数组 let arr = [] // 声明对象 let obj = {} // or let obj = null 复制代码
异常处理问题
我们在处理异步请求的时候,一定要对 response 中的数据进行异常处理,不然控制台回报 response.data is not undefined
,我们项目我看了下,有些地方没做处理,结果在做测试的时候,浏览器控制台一顿报错。那叫一个难看啊!
// incorrect this.axios(url, data, (response) => { let result = response.data.data }) // correct this.axios(url, data, (response) => { if (response.data && response.data.code === 1) { let result = response.data.data } }).catch(err => { console.error(err) }) 复制代码
如果这个取值过长且多次用到,请赋给一个变量
export default { ... methods: { handleClick (evt) { // incorrect evt.target.parentNode.innerHTML = 'test' evt.target.style.width = '100px' evt.target.style.height = '200px' // correct let target = evt.target target.parentNode.innerHTML = 'test' target.style.width = '100px' target.style.height = '200px' } } } 复制代码
6. HTML
正确的使用标签
项目中我见有人写个按钮居然用 span
标签,或者一个 div
。
下面是错误的范例:
// 用 div 当按钮 <div class="btn">搜索</div> // 在 span 里 嵌套 el-input 组件 // 这样做的同学,肯定不知道 el-input 编译后的代码是啥样的! <span> <el-input></el-input> </span> // 用 label 当标题 // label 标签是配合表单使用的 <label>标题</label> // 加粗字体没有用原生标签 <span class="bold">我是加粗字体</span> 复制代码
下面是改正后的范例:
// 用 H5 的 button <button class="btn">搜索</button> // 如果要包含 el-input 组件请使用块级元素,并加上合适的 class <div class="el-input__wrapper"> <el-input></el-input> </div> // h1-h6 才是标题的正确打开方式 <h2>标题</h2> // 加粗字体请使用原生标签 // 然后使用 class 控制字体样式 <strong class="bold">我是加粗字体</strong> 复制代码
所有的按钮,超链接,鼠标的
:hover
状态都应该是手形。
a, button { cursor: pointer } 复制代码
id 和 class 或者其他的属性,命名要语义化
不要命个名只有你自己知道。这样会带来后期维护困难。
<!-- incorrect --> <div class="dfdf"> <el-form class="loginForm">...</el-form> </div> <!-- correct --> <div class="login-form__wrapper"> <el-form class="loginForm">...</el-form> </div> 复制代码
把代码缩进改成 2 个空格
Html 中的属性之间保留一个空格距离
<!-- incorrect --> <el-input v-model="form.loginUser" size="small" placeholder="请输入用户名"></el-input> <!-- 不觉的上面的代码很丑吗,我知道你或许不会这样做 --> <!-- 但还真有人这样做 --> <!-- 下面是改进后的代码 --> <el-input v-model="form.loginUser" size="small" placeholder="请输入用户名"></el-input> 复制代码
每个代码快尽量加上注释
代码量少尚且不说,如果一个 .vue 文件很长的话,找起来就很痛苦了。你还别说,我们项目里就是这样没注释。
<!-- 正确的示范 --> <template> <div class="user-managerment__wrapper"> <!-- Header --> <div class="header">...</div> <!-- User table --> <div class="user-table__wrapper"> <el-table>...</el-table> </div> <!-- Add user dialog --> <div class="add-user__dialog"> <el-dialog title="新增用户">...</el-dialog> </div> </div> </template> 复制代码
7. CSS
{
和选择器保持一个空格距离
.selector { ... } 复制代码
给每个样式模块加上注释有助于区分
// Global style html, body, a, div { margin: 0 } // Login style .login button { ... } // User manager style .user-manager__wrapper { ... } 复制代码
每个独立样式间保留一行距离
见上面的示例
选择器不要嵌套太多层级
嵌套太多层级会影响性能,尽量保证在三层以下:
// incorrect .user-management .user-box .user-form .el-form-item .remark { color: #42b983 } // correct .user-management .user-form .remark { color: #42b983 } 复制代码
8. Git 代码提交
提交前先 pull 代码
写代码前记得先 pull 下别人的代码,这是个好习惯。别等到自己写完 push 后才发现代码有冲突。
# pull git pull # modified git add someFiles git commit -m "..." git push 复制代码
写好提交注释
大家可以看我 沸点。同事写的注释。希望有问题的同学可以及时改正哦。另外,关于 Git 如何正确的写好注释,这里有几篇文章讲的很好,大家可以看看:
下面举个例子,比如我这次在用户管理模块中修改了两个 bug。如何以清单的方式提交呢? 看代码:
# add file git add src/components/userManager/index.vue # commit git commit -m 'fix: 用户管理模块bug修改。 修改内容: - 修改了列表分页的bug - 修改了当用户点击编辑按钮弹框无法显示的bug ' # push code git push 复制代码
你千万别用下面的方式之一去提交你的代码说明:
# 说一些毫无意义的内容 git commit -m "fix: ok!" # or 不加 fix、feat、refactor、doc、style等前缀 # 为什么要加这些前缀呢?问得好! # 是方便日后检索,当我们以这些前缀去搜索修改日志的时候 # 是很容易的哦,微笑。 git commit -m "修改用户模块bug"
编辑:--ns868