虚拟列表与普通列表的性能对比

609 阅读1分钟

什么是虚拟列表?

根据滚动容器的可视区域渲染长列表的某一个部分数据的技术,即长列表的按需渲染

普通长列表的性能消耗

首先创建一个普通长列表,包含5000项

import React from 'react'

export default class App extends React.Component {
  constructor (props) {
    super(props)
    this.state = {
      complexDOMs: []
    }

    this.onCreateComplexDOMs = this.onCreateComplexDOMs.bind(this)
  }

  onCreateComplexDOMs () {
    this.setState({
      complexDOMs: Array.from(Array(5000).keys())
    });
  }

  render () {
    const {complexDOMs}=this.state;
    return (
      <div style={{ marginLeft: '10px' }}>
        <h3>Creat large of DOMs:</h3>
        <button onClick={this.onCreateComplexDOMs}>Create Complex DOMs</button>
        <div>{complexDOMs.map((item) => {
              return (
                <div key={item}>
                  <p>#${item} eligendi voluptatem quisquam</p>
                  <p>
                    Modi autem fugiat maiores. Doloremque est sed quis qui
                    nobis. Accusamus dolorem aspernatur sed rem.
                  </p>
                </div>
              );
            })}</div>
      </div>
    )
  }
}import React from 'react'

function createMarkup (doms) {
  return doms.length ? { __html: doms.join(' ') } : { __html: '' }
}

export default class App extends React.Component {
  constructor (props) {
    super(props)
    this.state = {
      complexDOMs: []
    }

    this.onCreateComplexDOMs = this.onCreateComplexDOMs.bind(this)
  }

  onCreateComplexDOMs () {
    const array = []
    for (var i = 0; i < 5000; i++) {
      array.push(`
        <div class='list-item'>
          <p>#${i} eligendi voluptatem quisquam</p>
          <p>Modi autem fugiat maiores. Doloremque est sed quis qui nobis. Accusamus dolorem aspernatur sed rem.</p>
        </div>
      `)
    }

    this.setState({
      complexDOMs: array
    })
  }

  render () {
    return (
      <div style={{ marginLeft: '10px' }}>
        <h3>Creat large of DOMs:</h3>
        <button onClick={this.onCreateComplexDOMs}>Create Complex DOMs</button>
        <div dangerouslySetInnerHTML={createMarkup(this.state.complexDOMs)} />
      </div>
    )
  }
}

image.png
消耗了808ms

使用虚拟列表技术展示上述列表

import React from "react";

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      complexDOMs: [],
      start: 0,  // 可见列表的第一项的索引
      end: 0     // 可见列表的最后一项的索引
    };
    this.containerRef = React.createRef();
    this.onCreateComplexDOMs = this.onCreateComplexDOMs.bind(this);
  }

  componentDidMount() {
    this.calculateRange();
  }
  
  // 全部可容纳的列表项的数量
  getOffset = (scrollTop) => {
    // 为了简化,便于理解,这里我们假设列表项高度为79
    return Math.floor(scrollTop / 79);
  };

 // 可见范围的列表项数量
  getViewCapacity = (containerHeight) => {
    // 假设列表项高度为79
    return Math.ceil(containerHeight / 79);
  };

  // 触发scroll时,实时更新展示范围
  calculateRange = () => {
    const overscan = 10; // 不在显示区域的缓冲值
    const ele = this.containerRef.current;
    if (ele) {
      const offset = this.getOffset(ele.scrollTop); // 容器能容纳的列表项数
      const viewCapacity = this.getViewCapacity(ele.clientHeight); // 显示区域能容纳的列表项数
      const from = offset - overscan; // 从哪一项开始展示
      const to = offset + viewCapacity + overscan; // 从哪一项结束
      this.setState({
        start: from > 0 ? from : 0,
        end: to < 5000 ? to : 5000
      });
    }
  };

  onCreateComplexDOMs = (e) => {
    e && e.preventDefault();
    this.setState({
      complexDOMs: Array.from(Array(5000).keys())
    });
  };

  render() {
    const { start, end, complexDOMs } = this.state;
    return (
      <>
        <button onClick={this.onCreateComplexDOMs}>add virtualList</button>
        <div
          className="container"
          ref={this.containerRef}
          onScroll={this.calculateRange}
          style={{ height: "200px", overflow: "auto" }}
        >
          {/* 预估wrapper高度,修改height改变滚动进度条的长度 */}
          {/* 修改margintop留出wrapper到顶部的距离 */}
          <div
            className="wrapper"
            style={{
              height: complexDOMs.length * 79 - start * 79,
              marginTop: start * 79
            }}
          >
          	{/* 展示可见范围的列表项 */}
            {complexDOMs.slice(start, end).map((item) => {
              return (
                <div key={item}>
                  <p>#${item} eligendi voluptatem quisquam</p>
                  <p>
                    Modi autem fugiat maiores. Doloremque est sed quis qui
                    nobis. Accusamus dolorem aspernatur sed rem.
                  </p>
                </div>
              );
            })}
          </div>
        </div>
      </>
    );
  }
}

在使用performace测试一下
image.png
只消耗了16.7ms

参考文章:github.com/dwqs/blog/i…