背景
element-plus中,el-select中可选项达到了500条,页面跳转销毁时导致异常卡顿(vue3.0.0版本,3.0.11版本有很大改善,建议升级)
因此需要进行分页操作。初步设想时当select中的options滚动到最底部的时候,触发加载更多,获取更多的可选项。
实现方式
打算通过指令实现,这样添加就很方便,预期一个指令
v-loadmore="loadMore"
搜一下
正常情况下,我们碰到的100个需求,99个都已经有人实现过了,所以我们就只需要搜一哈,就能找到答案。
果不其然,答案异常的多,只不过都是element-ui的,不过改动不多,应该问题不大,让我们来试一试。
开始踩坑
1.附上随处可搜的代码
我就不贴从哪抄的了,因为随处可以搜到
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,所以添加监听器就报错了
探寻不同
1.对比element-ui和element-plus的dom节点
左边是element-ui,右边是element-plus
节点都在,而且都在body下,按道理应该没问题
2.打印挂载的el节点
发现了没,plus下打印的节点,异常清爽,并且还有两行贴心的注释
<!--teleport start-->
<!--teleport end-->
3.vue3新增了Teleport
在vue3刚出来的时候,我读过一遍文档,依稀记得添加了一个神奇的控件Teleport
,可以把逻辑和展示分开,但是是它的逻辑在一块。
想必element-plus就是使用这种方式重构了select组件。
4.阅读源码
让我们翻开element-plus/packages/select/src/select.vue
映入眼帘的就是el-popper
我们再看看element-plus/packages/popper/src/index.vue
看来我们找到它了
5.定位联系
我们点击select
,唤起对应的待选列表
如果一个页面有多个select
,也是正常能唤起的
所以每个select
与每个popper
之间,应该存在着某种联系
继续阅读el-popper
,果然,我们发现了一个值popperId
并且被赋值给了ariaDescribedby
回过头来看dom🌲
我们果然在select的标签上,找到了ariaDescribedby
属性
并且再其对应的popper
上,找到了这个id
那么问题解决了,我们只需要对指令稍作修改
最终指令
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;