在业务中,我是如何实现虚拟滚动的(源码和解决方案) 上

963 阅读4分钟

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第4篇文章,点击查看活动详情

1. 什么是虚拟滚动

虚拟滚动指的是只渲染可视区域的列表项,非可见区域的完全不渲染,在滚动条滚动时动态更新列表项。

2. 为什么要进行虚拟滚动

根据自己的亲身经历,本人在公司进行某个项目的业务开发时,没有考虑到未来会有这么多的数据,直接滚动加载干了起来,结果在某一天,一个客户使用了我们的产品后,其大量的数据,滚呀滚,滚着滚着突然浏览器卡死崩溃掉了。我也跟着崩溃掉了。

而虚拟滚动,不管你有多少数据项,仅仅只会将可视窗口中的数据进行渲染。这样,就不怕崩溃了!

3. 如何实现

因为公司是基于antd进行ui开发,在查阅了antd的组件后,最终决定使用rc-virtual-list进行技术改造。

我们首先来看下rc-virtual-list的使用方法:

List

参数描述类型默认值
children需要进行虚拟滚动的列表数据(item, index, props) => ReactElement-
component自定义列表 dom 元素string | Componentdiv
data数据源Array-
height可视窗口的高度number-
itemHeight每一个item最小的高度number-
itemKey每一个item的keystring-
官方文档中还有`disabled`两个属性,但在查看源码后,发现应该已经没有用了

我们来看下官网提供的一个非常mini的demo

import List from 'rc-virtual-list';

<List data={[0, 1, 2]} height={200} itemHeight={30} itemKey="id">
 {index => <div>{index}</div>}
</List>;
上下对照,还是很好明白的
children =  {index => <div>{index}</div>}
data = [0, 1, 2]
height = 200
itemHeight = 30
itemKey = id     

一个最简单的虚拟滚动就实现了!

4. 深入了解rc-virtual-list

我们知道了可以基于rc-virtual-list开发自己的虚拟滚动列表组件,但是该组件是如何实现虚拟滚动的呢?我们来一起研究一下! 我们基于官网的提供的demo进行源码探究,来一起看看如何实现虚拟滚动这个🐂🆚效果的吧

image.png

入口文件

我们从入口文件进入

// examples/height.tsx
<List
  data={data}
  height={500}
  itemHeight={30}
  itemKey="id"
  style={{
    border: '1px solid red',
    boxSizing: 'border-box',
  }}
>
  {item => <ForwardMyItem {...item} />}
</List>

// 双数列高度为 30,单数列为 30+70 = 100
const data: Item[] = [];
for (let i = 0; i < 100; i += 1) {
  data.push({
    id: i,
    height: 30 + (i % 2 ? 70 : 0),
  });
}

// item组件,充当list的children
const MyItem: React.ForwardRefRenderFunction<HTMLElement, Item> = ({ id, height }, ref) => {
  return (
    <span
      ref={ref}
      style={{
        border: '1px solid gray',
        padding: '0 16px',
        height,
        lineHeight: '30px',
        boxSizing: 'border-box',
        display: 'inline-block',
      }}
    >
      {id}
    </span>
  );
};

const ForwardMyItem = React.forwardRef(MyItem);

这里是组件调用的地方,我们可以看到每一个chidren的结构,并且单双列的宽度,最最最重要的组件,我们进入到List进行进一步的查阅。

List组件

这是rc-virtual-list的本体,让我们好好研究一下吧! 这里,我们结合官网提供的demo,来展示一下虚拟滚动的dom结构:

image.png

进一步的我们根据dom和List组件最终return的产出,两者对比着看!!

return (
// 对应着class为rc-virtual-list jiangniao,是虚拟滚动组件最外层父组件
    <div
      style={{
        ...style,
        position: 'relative',
      }}
      className={mergedClassName}
      {...restProps}
    >
      <Component
        className={`${prefixCls}-holder`}
        style={componentStyle}
        ref={componentRef}
        onScroll={onFallbackScroll}
      >
        <Filler
          prefixCls={prefixCls}
          height={scrollHeight}
          offset={offset}
          onInnerResize={collectHeight}
          ref={fillerInnerRef}
        >
          {listChildren}
        </Filler>
      </Component>

      {useVirtual && (
        <ScrollBar
          ref={scrollBarRef}
          prefixCls={prefixCls}
          scrollTop={scrollTop}
          height={height}
          scrollHeight={scrollHeight}
          count={mergedData.length}
          onScroll={onScrollBar}
          onStartMove={() => {
            setScrollMoving(true);
          }}
          onStopMove={() => {
            setScrollMoving(false);
          }}
        />
      )}
    </div>
  );

整体介绍

我们根据demo的dom结构,进行进一步的查阅,可以发现几个关键的信息,也是我们接下来源码阅读要注意的:

该组件,一共五层,

第一层rc-virtual-list jiangniao,组件壳;

第二层rc-virtual-list-holderrc-virtual-list-scrollbar rc-virtual-list-scrollbar-show,前者是内容区,后者是滚动条;

第三层一个所有内容高度总和的div

image.png 第四层:rc-virtual-list-holder-inner虚拟滚动的可视区,也是最终dom渲染的地方;

第五层:展示在可视区域的dom元素 了解了组件的整体结构后,想必有几个问题在脑子中!

1. 第三层是哪里来的呢?仅仅查看list代码是无法体现出来的?
2. 如何渲染出当前可视窗口的dom元素,依据是什么?
3. 如何计算滚动条的高度?
4. 如何滚动?谁在滚动?

5.总结

本章简单介绍了解决方案,整体介绍了rc-virtual-list的结构。

因为实在是太长了,不得不拆成三部分,不然容易劝退!!更具体的五层介绍请看下一章。

在业务中,我是如何实现虚拟滚动的(源码和解决方案) 中

在业务中,我是如何实现虚拟滚动的(源码和解决方案) 下

资源引用

github.com/react-compo…

github.com/Nuibia/virt…