之前也有被问或者听说 如果给你一万条数据,你怎么渲染?,当时觉得为什么会这么问,不是有分页或者上拉加载吗?
分页和上拉加载确实能解决这个问题,不过这和虚拟列表是两码事。
解释: 不管是分页,还是上拉加载,有一万条数据其实就会渲染一万条dom,dom过多无疑对滚动的流畅性或性能都有一定的影响。
区别: 虚拟列表在于,哪怕有一万条数据,列表中只会渲染一部分dom(通常是正常可见的部分dom)。相信看到这里应该就明白不同之处了吧!
过程分析:
如上图,这是一个正常列表的模型,当我们往上滑动时,dom就会不断加载。对于用户而言,可视的区域始终只有6个(如:第0个~第5个),其他的,你渲染了,也是白渲染,所以我们可以只渲染可视的6个(为了流畅点,肯定不至于这么死板,一般多固定渲染一部分)
由于我们只只会渲染 8(举例8个) 个dom,但是我们的列表又要1w条的高度,还要滚动条,看起来才像真的。
因此,通常会计算出滚动dom高度
布局
<h3 class="title" >列表头部</h3> <div class="v-list-box" @scroll="onListScroll" > <div class="v-list" :style="vHeight" > <div v-for="item in renderArr" :style="item.style" class="item" > {{item.text}} </div> </div> </div> <h3 class="title" >列表尾部</h3>
高度计算
computed: { // 计算 v-list 本身高度 vHeight( { arr } ) { return { height: arr.length * 50 + 'px', } } },
对于数据,通常是数组,那么我们可以考虑把数组分割成块(假设始终只渲染8个,那么我们可以按长度为8来分割数组)
arrPipe(start = 0,end = start + 8) { let pipe = this.arr.slice(start,end) this.renderArr = pipe },
既然拿到了分块的数据,那么我们只需要在每次滚动后,就将对应区间的数据再次渲染上去,因此我们还需要监听下滚动事件,根据滚动的高度,进而判断滚动了几个dom,进而分割对应区间数据
onListScroll( { target: { scrollTop } } ) { // 计算切片 假设dom高度固定为50 let start = Math.floor(scrollTop / 50) // 小优化,只有当 上一个dom消失在可视区域,才进行更新 if(start != this.lastStart) { this.lastStart = start this.arrPipe(start) } },
按照上面的步骤,可以看到滚动时,dom能够及时更新,但是位置没有及时更替,始终在初始位置,因为我们只渲染了8个dom,所以并不会一次排列下来,所以需要对这些dom的位置进行位移计算
// 在渲染时,再次计算下对应的位置关系 style: { transform: `translateY(${v * 50}px)`, position: 'absolute', width: '100%' }
结果如下:
总结:上述只是对虚拟列表实现方案的简单探索,优化的地方还有很多(还可以将每次消失的dom位移到下一个出现的位置,减少渲染)。现在已经有很多插件可以用,还支持不同的高度的dom渲染
如:vue-virtual-scroller(三方插件)、useVirtualList(VueUse)