「源码阅读系列」虚拟滚动 React-window (一)

181 阅读1分钟

react-window 使用示例:

// Demo.js

import React from 'react';
import { FixedSizeList as List } from 'react-window';

import './App.css';

const Row = ({ index, style }) => (
  <div className={index % 2 ? 'ListItemOdd' : 'ListItemEven'} style={style}>
    Row {index}
  </div>
);

const App = () => (
  <div className='container'>
    <List
      className="List"
      height={150}
      itemCount={1000}
      itemSize={35}
      width={300}
    >
      {Row}
    </List>
  </div>
);

虚拟滚动的基本原理是只展示可视区(窗口)部分的元素,从而避免一次性渲染大量元素造成页面渲染卡顿。让我们来看看react-window是如何实现虚拟滚动的

首先按照滚动方向,虚拟滚动组件分为List(x或y轴),Grid(x和y轴),先看List,核心代码在 src/createListComponent.js

// createListComponent.js

export default function createListComponent({
  getStartIndexForOffset,
  getStopIndexForStartIndex,
  ...
} => {
    /** 返回List组件 **/  
    ...
}

createListComponent的入参是一个init函数,这个init函数接受了 getStartIndexForOffset, getStopIndexForStartIndex 这两个参数, 这个两个参数也是一个函数,由外部调用createListCompoenent的组件提供

// FixedSizeList.js

const FixedSizeList = createListComponent({
  getStartIndexForOffset: (
    { itemCount, itemSize }: Props<any>,
    offset: number
  ): number =>
    Math.max(
      0,
      Math.min(itemCount - 1, Math.floor(offset / ((itemSize: any): number)))
    ),
  ...
}

它们的作用分别是提供计算从渲染的元素在总列表中的起始索引和结束索引

// createListComponent.js

_getRangeToRender(): [number, number, number, number] {
      ...
      // 调用init函数提供的 getStartIndexForOffset
      const startIndex = getStartIndexForOffset(
        this.props,
        scrollOffset,
        this._instanceProps
      );
      // 调用init函数提供的 getStopIndexForStartIndex
      const stopIndex = getStopIndexForStartIndex(...);
      return [startIndex, stopIndex]
}      

render() {
    const [startIndex, stopIndex] = this._getRangeToRender();

    if (itemCount > 0) {
        // startIndex和stopIndex决定了视图中渲染哪些元素
        for (let index = startIndex; index <= stopIndex; index++) {
          items.push(createElement(children, { index, ... }));
        }
    }
}