-
Notifications
You must be signed in to change notification settings - Fork 442
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
react-tiny-virtual-list的源码解读 #71
Comments
Open
👍 |
请问一下当滑动过快的时候,页面会空白,这种如何解决? |
看了下好像并没有解决动态高度的问题。
|
这是来自QQ邮箱的自动回复邮件。您好,您的邮箱我已经收到了,感谢您的来信!
|
可以初始化内容的总高度 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
前言
从文档来看,该库支持横向和纵向两个方向的滚动(通过
scrollDirection
属性设置,默认是垂直方向),我们选择 垂直方向 来分析。另外有两个需要说明的一个属性是
itemSize
和estimatedItemSize
。itemSize
用于设置列表项的高度:(index: number): number
如果不知道
itemSize
的值,则可用estimatedItemSize
属性给列表项元素一个预估的高度,这样就能预估高度计算列表内容的总高度,并且总高度随着列表项的渲染而渐进调整;这个在列表项是动态高度的场景下很有用,可以初始化内容的总高度以撑开容器元素,使其可在垂直方向滚动。初步了解这两个属性之后,我们先看下其采用的 DOM 结构。
内部的 DOM 结构
要了解组件的 DOM 结构,先看组件的 render 方法:
items
是可视区域内被渲染的元素列表。sizeAndPositionManager
是类SizeAndPositionManager
的一个实例,用于管理列表及列表项的大小和位置偏移:获取到预估大小之后,就能预估可滚动区域的总大小了:
如果列表项的预估高度是 100,总数据个数是 200,那初始化时的预估高度就是 (200 - (-1) - 1) * 100 = 20000,这样就可以撑开滚动容器元素。
计算 startIndex 和 endIndex
知道了该库怎么预估初始化高度的,接下来看看它是怎么计算
startIndex
和endIndex
的。我们继续看它的render
方法:从上述简化后的关键代码可以看到,该库会调用
getVisibleRange
方法来计算start
和stop
(即startIndex
和endIndex
),然后就可以利用这两个边界值来计算可视区域渲染的元素了。这里的关键方法是getVisibleRange
,它有三个参数:containerSize
、overscanCount
以及offset
,前两个都是通过props
读取的,offset
是从state
中读取的:如果没有设置
scrollOffset
和scrollToIndex
属性,offset
的默认值是 0。从文档来看,scrollOffset
是设置滚动容器元素默认的垂直(或水平)偏移,scrollToIndex
是设置默认滚动到哪个元素。如果设置了scrollToIndex
,则会调用getOffsetForIndex
方法获取到该索引对应元素的偏移,因而可以认为offset
是滚动容器元素的垂直/水平偏移,即scrollTop/scrollLeft
的值。接着看
getVisibleRange
的实现:到这里,列表怎么在初始化渲染时怎么获取到可视区域内需要被渲染的元素就基本讲清楚了。那么,当用户滚动时,是怎么改变可视区域内需要被渲染的元素的呢?
滚动处理
我们看一下
scroll
事件的处理函数:当用户滚动时,会更改
offset
值,因而组件会重新渲染,进而会重新根据新的offset
去计算新的start
和stop
值。start
和stop
的值变了,就会改变可视区域内需要被渲染的元素。如何处理列表项的动态高度?
要处理列表项的动态高度,关键在于
itemSize
属性。itemSize
它用于设置列表项的高度:在列表项是动态高度的场景下,
itemSize
的值或是一个包含所有列表项高度的数据,或者一个根据索引返回类表项高度的函数。如果是数组,则需要知道每个列表项的高度或者列表项的高度有一定的规律,这种场景是非常受限的;如果是函数,只需要返回一个高度值就行,但元素未渲染到页面之前是无法得知其高度的,这个时候可以基于项目的实际情况,给列表项一个预估的高度:estimatedItemSize
。此外,还需要在每个列表项的大小发生改变时调用recomputeSizes
(见 recomputeSizes):上文说过,
lastMeasuredIndex
是最后一个被计算过大小的元素的索引。假设初始化渲染时的索引区间是 [0, 8],那在渲染完成之后,lastMeasuredIndex
的值是 8,当索引为 5 的元素的大小改变之后,那么 索引不小于 5 的所有元素的大小和偏移都需要重新计算,因为需要将lastMeasuredIndex
的值重置为 4。在渲染可是区域的元素时,我们可以缓存被渲染过元素的大小,当元素再次被渲染时,就可以直接通过缓存读取。
从上文可知,每个列表项都有内联的 style,会设置元素的
height
以及定位信息,而height
是通过itemSize
属性返回的,也就是说,该库对动态高度的支持也是需要使用者“显示”地返回每个列表项的高度,因而在列表项被渲染时,该列表项的高度就已经通过内联的样式固定了。而当元素实际渲染的内容偏少时,那其内容高度可能会小于给定的高度,就会造成大量的留白空间:
当元素实际渲染的内容偏多时,那其内容高度可能会大于给定的高度,就会造成内容的重叠:
列表项在渲染图片混合的场景下,内容重叠会更容易出现。因为图片存在网络请求,组件内部并没有相关的自我调整机制,而列表项在渲染时就给定了高度,这种场景下,内容重叠就很容易出现了。
总结
本文主要分析了虚拟组件库
react-tiny-virtual-list
的实现,经过上述分析,我们可以知道,该库实现虚拟列表的主要原理是根据state
的offset
值(即滚动容器元素的scrollTop/scrollLeft
值)先计算出可视区域内第一个元素的start
值,然后根据start
对应元素的offset
以及容器元素的大小,计算出当前可视区域内最后一个可见元素的索引,即stop
值。有了start
和stop
值,就可以改变可视区域需要渲染的内容了。在处理动态高度时,我分析了其不足之处,并通过一个 Demo 简单分析了在项目中如何使用它。此外,如果你需要使用这个组件,下面两个问题可能也是需要你考虑的:
One More Thing
原本下一篇文章想分享 [email protected] 组件的虚拟列表实现原理,但发现其与 react-tiny-virtual-list 组件无论是在 DOM 的布局上还是
start
以及stop
的计算规则上,实现思路基本是一样的,所以就不展开细讲了。这里列举部分我关注到的不同点:div
onItemsRendered
和onScroll
,react-window 在实现上通过 memoize-one 实现了计算缓存,而 react-tiny-virtual-list 则是直接调用itemSize
仅支持数值或函数,不支持数组renderItem
回调,而 react-window 是通过React.createElement
,相对而言,后者相对受限<本文完>
The text was updated successfully, but these errors were encountered: