前言:最近,在网站上查找实用的后台UI框架的时候,发现了vue-element-admin框架,然后,发现这款框架从2017年开始就发布了,作者随手出了几篇实用教程文档,看完以后深受启发,有种相恨见晚的感觉,特在此记录一下。
1.Watch immediate
这个算是一个比较常见的技巧了,当watch一个变量的时候,初始化时并不会执行,如下面的例子,你需要在created的时候手动调用一次。
// bad
created() {
this.fetchUserList();
}
watch: {
searchText: 'fetchUserList',
}
你可以添加immediate属性,这样初始化的时候也会触发,然后,上面的代码就能简化为:
// good
watch: {
searchText: {
handler: 'fetchUserList',
immediate: true,
}
}
PS:watch还有一个容易被大家忽略的属性deep。当设置为true时,它会进行深度监听。简而言之,就是你有一个const obj={a:1,b:2},里面任意一个key的value发生变化的时候都会触发watch。应用场景:比如我有一个列表,它有一堆query筛选项改变的时候,就自动请求新的数据。或者,你可以deep watch一个form表单,当任何一个字段内容发生变化的时候,你就帮它做自动保存等等。
2.Attrs和Listeners
这两个属性是vue2.4版本之后提供的,它简直是二次封装组件或者说写高阶组件的神器。在我们平时写业务的时候,免不了需要对一些第三方组件进行二次封装。比如,我们需要基于el-select分装一个带有业务特性的组件,根据输入的name搜索用户,并将一些业务逻辑分装在其中。但el-select这个第三方组件支持几十个配置参数,我们当然可以适当的挑选几个参数通过props来传递,但万一哪天别人用你的业务组件的时候,觉得你的参数少了,那你只能改你封装的组件了,亦或是哪天第三方组件加入了新参数,你该怎么办?
其实,我们的这个组件只是基于el-select做了一些业务的封装,比如添加了默认的placeholder,封装了远程ajax搜索请求等等,总的来说它就是一个中间人组件,只负责传递数据而已。
这时候,我们就可以使用v-bind=" a t t r s " : 传 递 所 有 属 性 、 v − o n = " attrs":传递所有属性、v-on=" attrs":传递所有属性、v−on="listeners"传递所有方法。如下图所示:
这样,我们没有在 p r o p s 中 声 明 的 方 法 和 属 性 , 会 通 过 props中声明的方法和属性,会通过 props中声明的方法和属性,会通过attrs、$listeners直接传递下去。这两个属性,在我们平时封装第三方组件的时候非常有用。
3.sync
这个也是vue 2.3之后,新加的一个语法糖。这也是平时在封装组件的时候,很好用的一个语法糖,它的实现机制和v-model是一样的。
当你有需要在子组件修改父组件值的时候,这个方法很好用。
4.Computed的get和set
computed大家肯定都用过,它除了可以缓存计算属性外,它在处理传入数据和目标数据格式不一致的时候,也是很有用的。
上面说的可能还是有点抽象,举一个简单的例子:我们有一个form表单,form里面有一个记录创建时间的字段create_at。我们知道前端的时间戳都是13位的,但很多后端默认时间戳是10位的。前端和后端的时间戳位数不一致。最常见的做法如下:
上面的代码主要做的是:在拿到数据的时候,将后端10位时间戳转化为13位时间戳,之后再向服务端发送数据的时候,再转化回10位时间戳传给后端。目前,这种做法当然是可行的,但之后可能不仅只有创建接口,还有更新接口的时候,你还需要在update的接口里在做一遍同样的数据转化的操作么?而且,这只是一个最简单的例子,真实的form表单会复杂的多,需要处理的数据也更为的多。这时候,代码就会变得很难维护。
这时候,就可以使用computed的set和get方法了。
通过上面的代码可以看到,我们把需要做前后端兼容的数据,放在了computed中,从getData和submit中隔离了数据处理的部分。
当然上面说的方案还不是最好的方案,你其实应该利用之前所说的v-bind=" a t t r s " 和 v − o n = " attrs"和v-on=" attrs"和v−on="listeners"对时间选择器组件进行二次封装。例如这样外部无需做任何数据处理,直接传入一个10位时间戳,内部进行转化。当日期发生变化的时候,自动通过emit触发input使v-model发生变化,把所有脏活累活都放在组件内部完成,保持外部业务代码的相对干净。具体v-model语法糖原理可以见官方文档。
set和get处理可以做上面说的进行一些数据处理之外,你也可以把它当做一个watch的升级版。它可以监听数据变化,当发生变化时,做一些额外的操作。最经典的用法就是v-model上绑定一个vuex值的时候,input发生变化时,通过commit更新存在vuex里面的值。
5.Object.freeze
这算是一个性能优化的小技巧吧。在我们遇到一些big data的业务场景,它就很有用了。尤其是做管理后台的时候,经常会有一些超大数据量的table,或者一个含有n多数据的图表,这种数据量很大的东西使用起来最明显的感受就是卡。但其实很多时候,这些数据其实并不需要响应式变化,这时候你就可以使用Object.freeze方法了,它可以冻结一个对象(注意它并不是vue特有的api)。
当你把一个普通的JavaScript对象传给Vue实例的data选项,Vue将遍历此对象所有的属性,并使用O bject.defineProperty把这些属性全部转为getter/setter,它们让Vue能进行追踪依赖,在属性被访问和修改时通知变化。使用了Object.freeze之后,不仅可以减少observer的开销,还能减少不少内存开销。
使用方式:this.item = Object.freeze(Object.assign({ },this.item))
6.Functional
函数式组件,但其实很少人会刻意的去使用。因为你不用它,代码也不会有任何问题,用了可能会出现bug。
当然,很多人会觉得我的项目中,也没有这种变化量级。比如:能用v-show的地方就不要用v-if,善用keep-alive和v-once,Object.freeze() 处理vue big data问题等。虽然,都是一些小细节,但对性能和体验都是有不少的提升的。
7.减少全局操作
这其实并不只是针对vue项目的一个建议,我们平时写代码的时候,一定要尽量避免一些全局的操作。如果必须要用到的时候,一定要自己检查,会不会产生一些全局的污染或者副作用。
举几个简单的例子:
①我们现在虽然用vue写代码了,核心思想转变为数据驱动view,不用像jQuery时代那样,频繁的操作DOM节点。但还是免不了有些场景还是要操作DOM的。我们在组件内选择节点的时候,一定要切记避免使用document.querySelector()等一系列的全局选择器。你应该使用this. e l 或 者 t h i s . r e f s . x x x . el 或者 this.refs.xxx. el或者this.refs.xxx.el 的方式来选择DOM。这样,就能将你的操作局限在当前的组件内,能避免很多问题;
②我们经常会不可避免的需要注册一些全局性事件,比如监听页面窗口的变化window.addEventListener(‘resize’,this._resizeHandler),但再声明了之后一定要在beforeDestroy或者destroyed生命周期注销它。window.removeEventListener(‘resize’,this._resizeHandler)避免造成不必要的消耗。
③避免过多的全局状态,不是所有的状态都需要存在vuex中的,应该根据业务进行合理的取舍。如果不可避免有很多的值需要存在vuex中,建议使用动态注册的方式相关文档。只是部分业务需要的状态处理,建议使用Event Bus或者使用简单的store模式。
④css也应该尽量避免写太多的全局性的样式。除了一些全局公用的样式外,所以针对业务或者组件的样式,都应该使用命名空间的方式或者直接使用vue-loader提供的scoped写法,避免一些全局冲突。
8.Sass和Js之间变量共享
这个需求可能有些人还没有遇到过,举个实际例子来说明一下。
如上面要实现一个动态的皮肤,就需要将用户选择的theme主题色传递给css。但同时初始化的时候css又需要将一个默认主题色传递给js。所以下面我们就分两块来讲解。
<div :style="{'background-color':color}" ></div>
或者使用css var(),还有用less的话modifyVars,等等方案都能实现js与css的变量与传递。
//var.scss
$theme:blue;
:export {
theme: $theme;
}
//test.js
import variables from '@/styles/var.scss'
console.log(variables.theme) //blue
当js和css共享一个变量的时候,这个方案还是很实用的。vue-element-admin中的侧边栏的宽度,颜色等等变量都是通过这种方案来实现共享的。
8.自动注册全局组件
因为业务场景大部分是中后台,虽然封装和使用了很多第三方组件,但还是免不了需要自己封装和使用很多业务组件。但每次用的时候还需要手动引入,真的是有些麻烦的。
我们其实可以基于webpack的require.context来实现自动加载组件并注册的全局的功能。相关原理在之前的文章中已经阐述过了。具体代码如下
我们可以创建一个GloableComponents文件夹,将你想要注册到全局的组件都放在这个文件夹里,在index.js里面放上如上代码。之后只要在入口文件main.js中引入即可。
//main.js
import './components/Table/index' // 自动注册全局业务组件
这样,我们可以在模板中直接使用这些全局组件了,不需要再繁琐的手动引入了。
<template>
<div>
<user-select/>
<status-button/>
</div>
</template>
当然,也不要为了省事,啥组件都往全局注册,这样,会让你初始化页面的时候,你的处事initbundle很大。你应该就注册那些你经常使用且体积不大的组件。那些体积大的组件,如编辑器或者图表组件还是按需加载比较合理。而且,你最好声明这些全局组件的时候,有一个统一的命名规范比如:globel-user-select这样的,指定一个团队规范,不然人家看到你这个全局组件会一脸懵逼,这个组件是哪来的。
9.Lint
这又是一个老生常谈的问题了,vue的一些最佳实践什么的话,这里就不讨论了,我觉得看官方的风格指南差不多就够了。比如,避免v-if和v-for用在一起、元素特性的顺序这些等等规则,几十条规则,说真的写了这么久vue,我也只能记住一些常规的。什么属性的顺序啊,不太可能记住的。这种东西,还是交给程序来自动化才是更合理的选择。强烈推荐配置编辑器自动化处理。具体配置见文档。同时,建议结合Git Hooks配合在每次提交代码时对代码进行lint校验,确保所有提交到远程仓库的代码都符合团队的规范。它主要使用的工具是husky和lint-staged,详细文档见Git Hooks 。
10.Hook
这个是一个文档里没有写的api,但我觉得是一个很有用的api。比如,我们平时使用一些第三方组件,或者注册一些全局事件的时候,都需要在mouted中声明,在destroyed中销毁。但由于这个是写在两个生命周期内的,很容易忘记,而且大部分在创建阶段声明的内容都会有副作用,如果你在组件摧毁阶段忘记移除的话,会造成内存的泄露,而且都不太容易发现。如下代码:
react在新版本中也加入了useEffect,将以前的多个life-cycles合并、重组,使逻辑更加清晰,这里就不展开了。那vue是不是也可以这样做?后来,发现了一个新的api:$on(‘hook:xxx’)。有了它,我们就能将之前的代码用更简单和清楚地方式实现了。
和react的useEffect有异曲同工之妙。
而且,我们有了这个api之后,能干的事情还不止这个。有时候,我们会用一些第三方组件,比如我们有一个编辑器组件(加载比较慢,会有白屏),所以,我们在它渲染完成之前需要给它一个占位符,但可能这个组件并没有暴露给我们这个接口,当然,我们需要修改这个组件,在它创建的时候手动emit一个事件出去,然后在组件上监听它,比如:
当然这也是可行的,但万一还要监听一个更新或者摧毁的生命周期呢?其实,利用hook可以很方便的实现这个效果。
<edit @hook:updated="doSomething"/>
参考博客:
手摸手,带你用vue撸后台 系列五(v4.0新版本) https://juejin.cn/post/6844903840626507784#heading-17