跳到正文

手写练习/React

GitHub

手写虚拟列表 virtualList

困难React性能

代码实现

JSX65 行
/**
 * 虚拟列表的核心: 只渲染可视区域内的DOM元素
 * 核心结构:
 * 1. 外层容器:设置固定高度和overflow-y: auto,作为滚动容器
 * 2. 内层容器:设置总高度,总高度 = 总数据量 * 每项高度
 * 3. 可视区域内的列表项:根据滚动位置动态改变transform:translateY(...)或则会top值。使其永远处于视口中
 */

import React, { useState, useRef, useEffect } from "react";

const VirtualList = ({ listData, itemHeight, containerHeight }) => {
  const [visibleData, setVisibleData] = useState([]); //实际渲染的数据
  const [startOffset, setStartOffset] = useState(0); //偏移量
  const [startIndex, setStartIndex] = useState(0); //起始索引

  const containerRef = useRef(null);

  const visibleCount = Math.ceil(containerHeight / itemHeight); //计算可见数量

  //滚动函数
  const onScroll = () => {
    const scrollTop = containerRef.current.scrollTop; // 获取滚动位置

    //计算当前的起始索引 -- 应该显示第几个列表
    const currStartIndex = Math.floor(scrollTop / itemHeight);
    setStartIndex(currStartIndex);

    //计算当前结束索引
    const currEndIndex = currStartIndex + visibleCount;

    //更新实际渲染的数据
    setVisibleData(listData.slice(currStartIndex, currEndIndex + 1)); 

    //更新偏移量
    setStartOffset(currStartIndex * itemHeight); //偏移量 = 起始索引 * 每项高度
  };

  useEffect(() => {
    onScroll();
  }, [listData, itemHeight, containerHeight]);

  return (
    <div
        ref={containerRef}
        style={{height: `${containerHeight}px`, overflow: "auto", position: "relative"}}
        onScroll={onScroll}
        >

        <div style={{height: `${listData.length * itemHeight}px`, position: "absolute", left: 0, top: 0, right: 0, zIndex: -1}}>
        </div>

        {/**实际渲染层 */}
        <div style={{transform: `translate3d(0, ${startOffset}px, 0)`}}>
            {visibleData.map((item, index) => (
                <div key={startIndex + index} style={{height: `${itemHeight}px`, borderBottom: "1px solid #eee"}}>
                    {item.value}
                </div>
            ))}
        </div>

    </div>
  )
};

export default VirtualList;