一、template
语义化标签,避免乱嵌套,合理命名属性等等标准推荐的东西就不谈了。
模板部分帮助我们展示结构化数据,vue 通过数据驱动视图,主要注意一下几点
- v-show,v-if 用哪个?在我来看要分两个维度去思考问题,第一个维度是权限问题,只要涉及到权限相关的展示无疑要用
v-if
,第二个维度在没有权限限制下根据用户点击的频次选择,频繁切换的使用v-show
,不频繁切换的使用v-if
,这里要说的优化点在于减少页面中 dom 总数,我比较倾向于使用v-if
,因为减少了 dom 数量,加快首屏渲染,至于性能方面我感觉肉眼看不出来切换的渲染过程,也不会影响用户的体验。 - 不要在模板里面写过多的表达式与判断
v-if="isShow && isAdmin && (a || b)"
,这种表达式虽说可以识别,但是不是长久之计,当看着不舒服时,适当的写到 methods 和 computed 里面封装成一个方法,这样的好处是方便我们在多处判断相同的表达式,其他权限相同的元素再判断展示的时候调用同一个方法即可。 循环调用子组件时添加 key。
- key 可以唯一标识一个循环个体,可以使用例如
item.id
作为 key。 - 在列表数据进行遍历渲染时,给每一项item设置唯一key值,会方便vue内部机制精准找到该条列表数据。当state更新时,新的状态值和旧的状态值对比,较快地定位到diff。
- key 可以唯一标识一个循环个体,可以使用例如
二、style
- 将样式文件放在 vue 文件内还是外?讨论起来没有意义,重点是按模块划分,建议是放在 vue 文件内部,方便写代码是在同一个文件里跳转上下对照,然后加上
<style scoped>
将样式文件锁住,目的很简单,再好用的标准也避免不了多人开发的麻烦,约定命名规则也可能会冲突,锁定区域后尽量采用简短的命名规则,不需要.header-title__text
之类的 class,直接.title
就好。 - 为了和上一条作区分,说下全局的样式文件,全局的样式文件,尽量抽象化,既然不在每一个组件里重复写,就尽量通用,这部分抽象做的越好说明你的样式文件体积越小,复用率越高。建议将复写组件库如 Element 样式的代码也放到全局中去。
- 不使用 float 布局,之前看到很多人封装了 .fl — float: left 到全局文件里去,然后又要 .clear,现在的浏览器还不至于弱到非要用 float 去兼容,完全可以 flex,grid 兼容性一般,功能其实 flex 布局都可以实现,float 会带来布局上的麻烦,用过的都知道。
- 至于其他通用的规范这里不赘述。
三、script
这部分也是最难优化的点,说下个人意见吧。
- 多人开发时尽量保持每个组件 export default {} 内的方法顺序一致,方便查找对应的方法。例如 name、data、props、components、mixins、钩子、computed、watch、methods。
- data 里要说的就是初始化数据的结构尽量详细,命名清晰,简单易懂,避免无用的变量,isEditing 实际可以代表两个状态,true 或 false,不要再去定义 notEditing 来控制展示,完全可以在模板里
{{ isEditing ? 编辑中 : 保存 }}
- props 父子组件传值时尽量
:width="" :heigth="" 不要 :option={}
,细化的好处是只传需要修改的参数,在子组件 props 里加数据类型,是否必传,以及默认值,便于排查错误,让传值更严谨。 - 钩子理解好生命周期的含义就好,什么时间应该请求,什么时间注销方法,哪些方法需要注销。简单易懂,官网都有写。
- methods 中每一个方法一定要简单,只做一件事,尽量封装可复用的简短的方法,参数不易过多。如果十分依赖 lodash 开发,method 看着会简洁许多,代价就是整体的 bundle 体积会大,假如项目仅仅用到小部分方法可以局部引入 loadsh,不想用 lodash 的话可以自己封装一个 util.js 文件
- watch 和 computed 用哪个的问题看官网的例子,建议能用computed的不要用watch,过多用watch会影响性能。计算属性主要是做一层 filter 转换,切忌加一些调用方法进去,watch 的作用就是监听数据变化去改变数据或触发事件如
this.$store.dispatch('update', { ... })
- 减少watch的数据。当组件某个数据变更后需要对应的state进行变更,就需要对另外的组件进行state进行变更。可以使用watch监听相应的数据变更并绑定事件。当watch的数据比较小,性能消耗不明显。当数据变大,系统会出现卡顿,所以减少watch的数据。其它不同的组件的state双向绑定,可以采用事件中央总线或者vuex进行数据的变更操作。
四、组件优化
在项目开发过程之中,如果把所有的组件的布局写在一个组件中,当数据变更时,由于组件代码比较庞大,vue 的数据驱动视图更新会比较慢,造成渲染过慢,也会造成比较差的体验效果。所以要把组件细分,比如一个组件,可以把整个组件细分成轮播组件、列表组件、分页组件等。
组件细化是有必要的,那组件细化到什么程度算是合理,还要因项目大小而异,小型项目可以简单几个组件搞定,甚至不用 vuex,axios 等等,如果规模较大就要细分组件,越细越好,包括布局的封装,按钮,表单,提示框,轮播等,推荐看下 Element 组件库的代码,没时间写这么详细可以直接用 Element 库,分几点进行优化:
- 组件有明确含义,只处理类似的业务。复用性越高越好,配置性越强越好。
- 自己封装组件还是遵循配置 props 细化的规则。
- 组件分类,建议路由控制的每个模块都新建一个文件夹,然后对其每个组件再进行细化,一些小的组件,例如 icon, scrollTop 等,可以单独新建一个文件夹 layout 存放。
五、组件的异步加载(按需加载组件)
在平时的demo中,可能不会遇见这个需求。当页面很多,组件很多的时候,SPA页面在首次加载的时候,就会变的很慢。这是因为vue首次加载的时候把可能一开始看不见的组件也一次加载了,这个时候就需要对页面进行优化,就需要异步组件了。
在路由index.js里(路径: src/router/index.js
),通过require方式或者import()方式动态加载组件。像下面这样:
{
path: '/home',
name: 'home',
component:() => import('@views/home')
}
六、vue-router 和 vuex 优化
vue-router 除了切换路由,用的最多的是处理权限的逻辑,关于权限的控制这里不赘述,相关 demo 和文章有许多,那么说到优化,值得一提的就是 组件懒加载
中文官网链接如上,例子如下
const Foo = r => require.ensure([], () => r(require('./Foo.vue')), 'group-foo')
const Bar = r => require.ensure([], () => r(require('./Bar.vue')), 'group-foo')
const Baz = r => require.ensure([], () => r(require('./Baz.vue')), 'group-foo')
这段代码将 Foo, Bar, Baz 三个组件打包进了名为 group-foo 的 chunk 文件,当然啦是 js 文件
其余部分正常写就可以,在网站加载时会自动解析需要加载哪个 chunk,虽然分别打包的总体积会变大,但是单看请求首屏速度的话,快了好多。
vuex 面临的问题和解决方案有几点
- 当网站足够大时,一个状态树下,根的部分字段繁多,解决这个问题就要模块化 vuex,官网提供了模块化方案,允许我们在初始化 vuex 的时候配置 modules。每一个 module 里面又分别包含 state 、action 等,看似是多个状态树,其实还是基于 rootState 的子树。细分后整个 state 结构就清晰了,管理起来也方便许多。
- 由于 vuex 的灵活性,带来了编码不统一的情况,完整的闭环是 store.dispatch(‘action’) -> action -> commit -> mutation -> getter -> computed,实际上中间的环节有的可以省略,因为 API 文档提供了以下几个方法 mapState、mapGetters、mapActions、mapMutations,然后在组件里可以直接调取任何一步,还是项目小想怎么调用都可以,项目大的时候,就要考虑 vuex 使用的统一性,建议是不论多简单的流程都跑完整个闭环,形成代码的统一,方便后期管理,在我的组件里只允许出现 dispatch 和 mapGetters,其余的流程都在名为 store 的 vuex 文件夹里进行。
基于上面一条,说下每个过程里面要做什么,前后端数据一定会有不一致的地方,或是数据结构,或是字段命名,那么究竟应该在哪一步处理数据转换的逻辑呢?有人会说其实哪一步都可以实现,其实不然,建议如下
- 在发 dispatch 之前就处理好组件内需要传的参数的数据结构和字段名
- 到了 action 允许我们做的事情很多,因为这部支持异步,支持 state, rootState, commit, dispatch, getters,由此可见责任重大,首先如果后端需要部分其他 module 里面的数据,要通过 rootState 取值再整合到原有数据上,下一步发出请求,建议(async await + axios),拿到数据后进行筛选转换,再发送 commit 到 mutation
- 这一步是将转换后的数据更新到 state 里,可能会有数据分发的过程(传进一个 object 改变多个 state 中 key 的 value),可以转换数据结构,但是尽量不做字段转换,在上一步做
- 此时的 store 已经更新,使用 getter 方法来取值,token: state => state.token,单单的取值,尽量不要做数据转换,需要转换的点在于多个地方用相同的字段,但是结构不同的情况(很少出现)。
- 在组件里用 mapGetters 拿到对应的 getter 值。
七、打包优化
上面说了代码方面的规范和优化,下面说下重点的打包优化,前段时间打包的 vender bundle 足足 1.4M,app bundle 也有 270K,app bundle 可以通过组件懒加载解决,vender 包该怎么解决?
有人会质疑是不是没压缩或依赖包没去重,其实都做了就是看到的 1.4M。
解决方法很简单,打包 vender 时不打包 vue、vuex、vue-router、axios 等,换用国内的 bootcdn 直接引入到根目录的 index.html 中。
例如:
<script src="//cdn.bootcss.com/vue/2.2.5/vue.min.js"></script>
<script src="//cdn.bootcss.com/vue-router/2.3.0/vue-router.min.js"></script>
<script src="//cdn.bootcss.com/vuex/2.2.1/vuex.min.js"></script>
<script src="//cdn.bootcss.com/axios/0.15.3/axios.min.js"></script>
在 webpack 里有个 externals,可以忽略不需要打包的库
externals: {
'vue': 'Vue',
'vue-router': 'VueRouter',
'vuex': 'Vuex',
'axios': 'axios'
}
此时的 vender 包会非常小,如果不够小还可以拆分其他的库,此时增加了请求的数量,但是远比加载一个 1.4M 的 bundle 快的多。