我的GitHub
0%

无星的前端之旅(二十)-element-plus给el-select添加上拉加载

背景

element-plus中,el-select中可选项达到了500条,页面跳转销毁时导致异常卡顿(vue3.0.0版本,3.0.11版本有很大改善,建议升级)

因此需要进行分页操作。初步设想时当select中的options滚动到最底部的时候,触发加载更多,获取更多的可选项。

实现方式

打算通过指令实现,这样添加就很方便,预期一个指令

1
v-loadmore="loadMore"

搜一下

正常情况下,我们碰到的100个需求,99个都已经有人实现过了,所以我们就只需要搜一哈,就能找到答案。

1.png

果不其然,答案异常的多,只不过都是element-ui的,不过改动不多,应该问题不大,让我们来试一试。

开始踩坑

1.附上随处可搜的代码

我就不贴从哪抄的了,因为随处可以搜到

1
2
3
4
5
6
7
8
9
10
11
12
13
Vue.directive('loadMore', {
bind(el, binding) {
// 获取element-ui定义好的scroll盒子
const SELECTWRAP_DOM = el.querySelector('.el-select-dropdown .el-select-dropdown__wrap')
SELECTWRAP_DOM.addEventListener('scroll', function () {

const CONDITION = this.scrollHeight - this.scrollTop <= this.clientHeight
if (CONDITION) {
binding.value()
}
})
}
})

这个代码分析起来很简单

1.通过指令绑定的时候传递dom节点

2.再通过class选择器找到scroll的盒子节点

3.添加滚动监听事件

4.滚动到底,触发绑定事件

在element-ui上完美运行

但是

在element-plus不行,会提示找不到SELECTWRAP_DOM

因为SELECTWRAP_DOM为null,所以添加监听器就报错了

2.png

探寻不同

1.对比element-ui和element-plus的dom节点

3.png

左边是element-ui,右边是element-plus

节点都在,而且都在body下,按道理应该没问题

2.打印挂载的el节点

4.png

代码:element-ui

5.png

代码:element-plus

发现了没,plus下打印的节点,异常清爽,并且还有两行贴心的注释

1
2
<!--teleport start-->
<!--teleport end-->

3.vue3新增了Teleport

Teleport

在vue3刚出来的时候,我读过一遍文档,依稀记得添加了一个神奇的控件Teleport,可以把逻辑和展示分开,但是是它的逻辑在一块。

想必element-plus就是使用这种方式重构了select组件。

4.阅读源码

让我们翻开element-plus/packages/select/src/select.vue

6.png

映入眼帘的就是el-popper

我们再看看element-plus/packages/popper/src/index.vue

7.png

看来我们找到它了

5.定位联系

我们点击select,唤起对应的待选列表

如果一个页面有多个select,也是正常能唤起的

所以每个select与每个popper之间,应该存在着某种联系

继续阅读el-popper,果然,我们发现了一个值popperId

8.png

并且被赋值给了ariaDescribedby

回过头来看dom🌲

我们果然在select的标签上,找到了ariaDescribedby属性

9.png

并且再其对应的popper上,找到了这个id

10.png

那么问题解决了,我们只需要对指令稍作修改

最终指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const loadMore = {
mounted(el, binding) {
const child = el.querySelector('.select-trigger');
const id = child.getAttribute('ariadescribedby');
const poper = document.getElementById(id);
const SELECTDOWN_DOM = poper.querySelector('.el-scrollbar .el-select-dropdown__wrap');
// 这里不能使用箭头函数!
// eslint-disable-next-line func-names
SELECTDOWN_DOM.addEventListener('scroll', function () {
/**
* scrollHeight 获取元素内容高度(只读)
* scrollTop 获取或者设置元素的偏移值,
* 常用于:计算滚动条的位置, 当一个元素的容器没有产生垂直方向的滚动条, 那它的scrollTop的值默认为0.
* clientHeight 读取元素的可见高度(只读)
* 如果元素滚动到底, 下面等式返回true, 没有则返回false:
* ele.scrollHeight - ele.scrollTop === ele.clientHeight;
*/
const CONDITION = this.scrollHeight - this.scrollTop <= this.clientHeight;
if (CONDITION) {
binding.value();
}
});
},
};

export default loadMore;

over

我是阿星,阿星的阿,阿星的星!