最近回顾下长列表的一些优化方案,提起长列表,vue栈最多的就会涉及到 vue-virtual-scroller 此轮子。
为了理清更多“为什么”,于是对插件源码进行简单分析。
看到源码目录,主要有 RecycleScroller.vue
、DynamicScroller.vue
和DynamicScrollerItem.vue
这三个组件,然而RecycleScroller
为实现核心。
在demo上看到有两个不同的实现,他们两者之间的区别是什么呢?在应用上 RecycleScroller
需要item的高度为静态的,也就是列表每个item的高度都是一致的。而 DynamicScroller
就可以兼容item的高度为动态的。但是理论上 RecycleScroller
也可以实现动态高度的item,只要有方案计算到item的height就可以(DynamicScrollerItem
解决的就是这个问题)。
分析一个js库,我们需要从目录下的 package.json
分析对应的入口或者打包配置文件。
从目录 ./build
我们可以到对应的打包脚本,入口是 ./src/index.js
。打开 index.js
可以看到这个插件是以注册全局组件插件的方式实现相应需求。
function registerComponents (Vue, prefix) {
Vue.component(`${prefix}recycle-scroller`, RecycleScroller)
Vue.component(`${prefix}RecycleScroller`, RecycleScroller)
Vue.component(`${prefix}dynamic-scroller`, DynamicScroller)
Vue.component(`${prefix}DynamicScroller`, DynamicScroller)
Vue.component(`${prefix}dynamic-scroller-item`, DynamicScrollerItem)
Vue.component(`${prefix}DynamicScrollerItem`, DynamicScrollerItem)
}
简单分析完 index.js
后,再看 ./src/components/
目录下的Vue组件文件。
接下来就分析一下作为基底的 RecycleScroller.vue
文件。
<div
v-observe-visibility="handleVisibilityChange"
class="vue-recycle-scroller"
:class="{
ready,
'page-mode': pageMode,
[`direction-${direction}`]: true,
}"
@scroll.passive="handleScroll"
>
<!-- $slots.before -->
<!-- 列表循环item部分 -->
<!-- $slots.after -->
</div>
通过代码我们可以看到组件引入了自定义指令 v-observe-visibility
, 再看发现是通过 import { ObserveVisibility } from 'vue-observe-visibility'
加载指令。
computed: {
sizes () {
if (this.itemSize === null) {
// ...
for (let i = 0, l = items.length; i < l; i++) {
current = items[i][field] || minItemSize
if (current < computedMinSize) {
computedMinSize = current
}
accumulator += current
sizes[i] = { accumulator, size: current }
}
// ...
return sizes // 返回每个item带size(高度)的数组
}
return []
},
},
主要实现原理是通过 this.updateVisibleItems()
计算出startIndex, endIndex获取需要渲染的元素数组。此处还会涉及的一个视口的计算,相应值为 scroll: {start: xxx, end: xxx}
. 通过视口的可视范围计算出 startIndex,...endIndex
每个元素的对应样式值,这里是通过css3的transform
控制元素显示。代码是
style="ready ? { transform: `translate${direction === 'vertical' ? 'Y' : 'X'}(${view.position}px)` } : null"
就会想,长列表那么多数据,demo都生成1W+了,每个滚动都去遍历一次,那算法复杂度不就很高了吗。所以这里就需要相关的算法知识。
在计算过程中,使用了 二分法
提高程序的执行效率。
// Searching for startIndex
do {
oldI = i
h = sizes[i].accumulator
if (h < scroll.start) {
a = i
} else if (i < count - 1 && sizes[i + 1].accumulator > scroll.start) {
b = i
}
i = ~~((a + b) / 2)
} while (i !== oldI)
i < 0 && (i = 0)
startIndex = i
为什么可以使用这个呢。在computed: sizes[]
的时候就已经将item以 accumulator
从小到大排好序了。所以 this.sizes
是一个有序的列表。
分析完 RecycleScroller.vue
接下来就理解DynamicScroller.vue
和DynamicScrollerItem.vue
的代码。
看代码我们可以发现 DynamicScroller 的实现也依赖了 RecycleScroller.vue
。 是通过 DynamicScrollerItem.vue
实现获取每个item的height/width得到数组元素对应的size,再回归到 RecycleScroller.vue
的相应实现。
整体下来大概是这样一个情况,总结得有点乱。希望大家一起进步。
对于源代码很多人都抗拒,但这真的是一种很有效学习方式。