element-ui中解决因el-option数据量大导致页面卡顿和渲染慢
一、业务场景
在使用element-ui展示表格数据时,需要根据查询条件对数据进行搜索,此时需使用el-select,并且需要从服务端获取数据用el-option展示给用户选择,但是由于搜索条件使用的是业务的数据,所以数据量比较大,服务端返回的数据量达到了5000多条,还需要在下拉框中展示。
此时会出现两个问题:
1、直接影响:渲染会导致页面卡顿且需要很长时间等待,用户体验极差;
2、间接影响:展示出来后,用户进行选择后查询,这里没问题,但是如果想去关闭选项卡的时候发现,关闭得极慢,这也是由于el-option数据量过大导致,因为渲染出来5000多个dom节点在页面上,vue需要去销毁的时间也就更长,反应就更慢了;
二、相关知识
(一)Vue实现自定义指令(directive)
(1)自定义指令(directive)使用场景
Vue除了核心功能默认内置的指令 (v-model
和v-show
),Vue也允许注册自定义指令。在你需要对普通DOM元素进行底层操作的情况下,这时候就会用到自定义指令directive。Vue自定义指令有全局注册和局部注册两种方式。先来看看注册全局指令的方式,通过Vue.directive(id, [definition])
方式注册全局指令。然后在入口文件中进行 Vue.use()调用。
批量注册指令,新建directives/index.js
文件
(2)注册方式以及用法
directive
分为全局注册以及局部注册两种,注册后在标签上使用v-注册名
即可使用,在下例中,使用v-name
1 | // 全局注册 |
(3)钩子函数
在注册directive
时,一个指令定义对象可以提供如下几个钩子函数 (均为可选):
bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 。
componentUpdated: 指令所在组件的 VNode 及其子 VNode 全部更新后调用。
unbind: 只调用一次,指令与元素解绑时调用。
我们在钩子函数中可以获取到以下参数:
el :指令所绑定的元素,可以用来直接操作 DOM
binding:一个对象,包含以下 property:
name:指令名,不包括v-前缀
value:指令的绑定值,例如:v-my-directive="1 + 1"
中,绑定值为2
oldValue:指令绑定的前一个值,仅在update
和componentUpdated
钩子中可用。无论值是否改变都可用
expression:字符串形式的指令表达式。例如v-my-directive="1 + 1"
中,表达式为"1 + 1"
arg:传给指令的参数,可选。例如v-my-directive:foo
中,参数为"foo"
modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar
中,修饰符对象为{ foo: true, bar: true }
vnode:Vue 编译生成的虚拟节点
oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用
注意!除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。
举个例子,我们在项目中的按钮需要通过权限来控制是否渲染,那么我们可以做一个权限指令来判断是否渲染,
html:
1 | <template> |
javascript:
1 | // 模拟从后端拿到权限数据为一个数组,里面有新增以及编辑两个按钮权限: |
这样,就完成了一个按钮级别的权限管理,自定义指令当然还有更多的用处,比如按钮的点击波纹,按钮点击请求返回前的禁用,某节点加载时loading,如开头所说的,在你需要对普通DOM元素进行底层操作的情况下,这时候就会用到自定义指令directive
。
(二)Vue中实现函数的防抖、节流
(1)介绍
1、**防抖(debounce)**:触发高频事件后 n 秒内函数只会执行一次,如果 n 秒内高频事件再次被触发,则重新计算时间。举例:就好像在百度搜索时,每次输入之后都有联想词弹出,这个控制联想词的方法就不可能是输入框内容一改变就触发的,他一定是当你结束输入一段时间之后才会触发。
**节流(thorttle)**:高频事件触发,但在 n 秒内只会执行一次,所以节流会稀释函数的执行频率。举例:预定一个函数只有在大于等于执行周期时才执行,周期内调用不执行。就好像你在淘宝抢购某一件限量热卖商品时,你不断点刷新点购买,可是总有一段时间你点上是没有效果,这里就用到了节流,就是怕点的太快导致系统出现bug。
2、区别:防抖动是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段时间执行。
(2)使用场景
[1] DOM 元素的拖拽功能实现(mousemove)
[2] 搜索联想(keyup)
[3] 计算鼠标移动的距离(mousemove)
[4] Canvas 模拟画板功能(mousemove)
[5] 射击游戏的 mousedown/keydown 事件(单位时间只能发射一颗子弹)
[6] 监听滚动事件判断是否到页面底部自动加载更多:给 scroll 加了 debounce 后,只有用户停止滚动后,才会判断是否到了页面底部;如果是 throttle 的话,只要页面滚动就会间隔一段时间判断一次.
(3)函数的防抖
函数的防抖是在多少时间后再来执行函数,我们可以理解为这样的一种生活场景(坐升降电梯),在点击电梯的开门按钮后,电梯会开门,然后等待一段时间来关门。但是如果在等待的期间,有人再次点击开门按钮,那么电梯后继续等待关门时间,直到等待关门时间结束,没有人来点击开门按钮后,电梯才会开始工作。
第一次非立即执行
1 | export function _debounce(f, t){ |
对于有些场景来说,第一次我不需要等待,需要立即执行,例如:打开控制台获取窗口试图大小(这里我们需要一直改变窗口的大小,等停下来再次获取窗口视图大小)。
1 | export function _debounceFirstExe(f, t){ |
最终合并的代码
1 | export function _debounce(f, t,im = false){ |
函数防抖对于我们代码层面我们可以用在哪里呢?
在点赞、输入框校验、取消点赞、创建订单等发送网络氢气的时候,如果我们连续点击按钮,可能会发送多次请求。这个对于后台来说是不允许的。在鼠标每次resize/scroll
触发统计事件.
(4)函数节流
与函数防抖的胞兄,函数节流的原理也是大同小异,函数节流是在一定时间内我只会执行一次。
第一次非立即执行
1 | export function _throttle(f,t){ |
在效果中,我们点击了非常多次,但是就只执行了4次,因为我规定的时间是1000ms执行一次。这样也是减少了执行次数。
第一次立即执行版本
1 | export function _throttleFirstExt(f, t) { |
这里我们看到了,第一次点击会立马执行。
最终合并后的代码
1 | export function _throttle(f, t, im = false){ |
三、解决问题
由于5000多条数据用户不可能一直滚动下去找自己想要数据,记得设置filterable
属性,实现搜索功能.
如果仅设置element-ui
的filterable
属性,那么搜索的范围只有懒加载已滚动出的数据,导致搜索不全、不准确。为了解决这个问题,我们又继续使用了filter-method
属性并结合visible-change
事件,以及搜索输入时增加防抖进行优化。
最终代码:
在main.js中添加以下代码:
1 | Vue.directive('el-select-loadmore', { |
在utils/index.js下面添加_debounce
方法
1 | // 防抖流 |
业务模块的页面代码:
1 | <el-form-item label="物料:"> |