渲染十万条数据的方法之分批渲染

前言:前端渲染海量数据列表的情况可能不多,因为很多列表都采用了分页,但是在读写Excel表数据这种情况下,处理海量数据还是有可能会用到。渲染十万条数据,可以体现前端开发同学处理高性能渲染的能力,也是面试常常问到的问题。

对于一次性插入大量数据的情况,一般有两种做法:

  • 分批渲染
  • 虚拟列表

本次对使用 分批渲染 的方式进行实现
虚拟列表参考链接: 渲染十万条数据的方法之虚拟列表

分批渲染

React代码实现

  • requestAnimationFrame 是一个浏览器提供的 API,用于在下一次重绘之前执行回调函数。它通常用于优化动画和其他需要高频率更新的操作(参考: requestAnimationFrame原理和使用
  • generateUniqueKey 可以生成唯一key,使 diff 算法高效,原理参考: js生成唯一标识符(例如key或者id)
  • 由于结合动画帧进行递归分批渲染,数据量大,会处理比较长的时间,所以不要忘记处理内存泄漏,在页面卸载的时候用一个开关去跳出可能尚存的递归
import { useCallback, useEffect, useRef, useState } from 'react';

interface DataItem {
  key?: string;
  slogan: string;
  bgColor: string;
}

/**
 * 生成唯一 key,这里使用时间戳 + 随机数
 * 你也可以引入第三方库,如 uuid 或 nanoid,但这里为了减少依赖,直接使用 JS 生成
 * @returns
 */
const generateUniqueKey = () => {
  return `${new Date().getTime()}-${Math.random().toString(36).substr(2, 9)}`;
};

const DATA_LIST = [
  { slogan: '我爱学友', bgColor: 'green' },
  { slogan: '我爱德华', bgColor: 'blue' },
  { slogan: '我爱黎明', bgColor: 'red' }
];

const BatchPage = () => {
  const [list, setList] = useState<DataItem[]>([]);
  // 用于组件卸载后,清除异步操作,防止内存泄漏
  const isUnmountedRef = useRef<boolean>(false);

  // 默认 batchSize = 100,即时间分片的每片为 100,每个动画帧渲染 100 条数据,可以根据实际情况调整
  const renderBatch = useCallback(
    (restList: DataItem[], existingList: DataItem[], batchSize = 100) => {
      if (!Array.isArray(restList) || !restList?.length || isUnmountedRef.current) return;
      // splice 除了改变原始数组,还会返回删掉的数组
      const addList = restList.splice(0, batchSize);
      // requestAnimationFrame 是一个浏览器提供的 API,用于在下一次重绘之前执行回调函数。它通常用于优化动画和其他需要高频率更新的操作。
      requestAnimationFrame(() => {
        const newList = [...existingList, ...addList];
        setList(newList);
        renderBatch(restList, newList, batchSize);
      });
    },
    []
  );

  const handleClick = useCallback(() => {
    const jsMakeDataStartTime = new Date().getTime();
    console.log('点击按钮时间戳-------->', jsMakeDataStartTime);
    const data: DataItem[] = [];
    // 模拟生成 10 万条数据
    for (let index = 0; index < 100000; index++) {
      const item = DATA_LIST[Math.floor(Math.random() * DATA_LIST.length)];
      data.push({
        key: generateUniqueKey(),
        ...item
      });
    }
    const jsMakeDataEndTime = new Date().getTime();
    console.log('JS生成数据时间戳------>', jsMakeDataEndTime);
    console.log('JS生成数据时间间隔---->', jsMakeDataEndTime - jsMakeDataStartTime);
    renderBatch(data, []);
  }, [renderBatch]);

  useEffect(() => {
    if (!!list?.length) console.log('数据变化时间戳和长度-->', new Date().getTime(), list?.length);
  }, [list]);

  useEffect(() => {
    isUnmountedRef.current = false;
    return () => {
      isUnmountedRef.current = true;
    };
  }, []);

  // 本应使用【className + 引入样式文件】的方式,但为了直观,这里直接使用style演示
  return (
    <div
      style={{
        width: '100vw',
        height: '100vh',
        display: 'flex',
        flexDirection: 'column',
        flexWrap: 'nowrap'
      }}
    >
      <button
        style={{ padding: '12px', color: 'white', backgroundColor: 'black' }}
        onClick={handleClick}
      >
        生成海量数据&渲染
      </button>
      <div style={{ flex: '1', overflowY: 'auto' }}>
        {Array.isArray(list) &&
          list.map(item => {
            return (
              <div
                key={item?.key}
                style={{
                  backgroundColor: item?.bgColor,
                  marginTop: '6px',
                  color: 'white'
                }}
              >
                {item?.slogan}
              </div>
            );
          })}
      </div>
    </div>
  );
};

export default BatchPage;

实际效果

在这里插入图片描述

也有缺点:会有闪屏,这个闪屏是下拉太快导致的,无法规避

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值