You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React表格加载200-300行卡顿,已用useMemo/React.memo无效求优化

React大表格性能优化(200-300行卡顿解决)

你的问题核心在于两点:一是组件重渲染控制失效,二是大量DOM节点导致React diff和浏览器渲染压力过大。以下是针对性的优化方案:

1. 修复React.memo的无效问题

你之前用React.memo没效果,大概率是因为TableRow接收的tableHeaders是数组类型,默认浅比较会认为每次都是新引用,导致所有行强制重渲染。给组件加上自定义比较函数:

// 给TableHeader加memo,只关注表头文本变化
const TableHeader = React.memo(({item, index}) => {
  return <th>{item.Header}</th>
}, (prevProps, nextProps) => {
  return prevProps.item.Header === nextProps.item.Header;
});

// 给TableRow加memo,自定义比较逻辑
const TableRow = React.memo(({item, index, tableHeaders}) => (
  <tr>
    {tableHeaders.map((columnItem, colIndex) => (
      <td key={colIndex}>{item[columnItem.accessor]}</td>
    ))}
  </tr>
), (prevProps, nextProps) => {
  // 比较行数据是否一致,表头结构是否未变化
  const isItemSame = Object.is(prevProps.item, nextProps.item);
  const isHeadersSame = prevProps.tableHeaders.length === nextProps.tableHeaders.length && 
                        prevProps.tableHeaders.every((h, i) => h.accessor === nextProps.tableHeaders[i].accessor);
  return isItemSame && isHeadersSame;
});

2. 稳定props的引用

确保tableHeaders的引用不会在父组件每次渲染时都更新,用useMemo缓存:

const myComponent = () => {
  const [tableHeaders, setTableHeaders] = useState([]);
  const [tableData, setTableData] = useState([]);

  // 缓存tableHeaders,只有当原数据变化时才更新引用
  const memoizedHeaders = useMemo(() => tableHeaders, [tableHeaders]);

  return(
    <div>
      <table>
        <thead>
          <tr>
            {memoizedHeaders.map((item, index) => <TableHeader key={index} item={item} index={index} />)}
          </tr>
        </thead>
        <tbody>
          {tableData.map((item, index) => <TableRow key={item.id || index} item={item} index={index} tableHeaders={memoizedHeaders} />)}
        </tbody>
      </table>
    </div>
  )
}

3. 虚拟滚动(最有效的优化)

200-300行的表格,全部渲染DOM节点会让浏览器压力剧增。虚拟滚动只渲染可视区域的行,DOM数量瞬间降到几十行:

const myComponent = () => {
  const [tableHeaders, setTableHeaders] = useState([]);
  const [tableData, setTableData] = useState([]);
  const [visibleRange, setVisibleRange] = useState({ start: 0, end: 20 });
  const tableContainerRef = useRef(null);
  const rowHeight = 40; // 假设每行高度固定,可根据实际调整

  // 监听滚动事件,计算可视区域的行范围
  useEffect(() => {
    const handleScroll = () => {
      if (!tableContainerRef.current) return;
      const { scrollTop, clientHeight } = tableContainerRef.current;
      const start = Math.floor(scrollTop / rowHeight);
      // 多渲染5行避免滚动时出现空白
      const end = start + Math.ceil(clientHeight / rowHeight) + 5; 
      setVisibleRange({ 
        start: Math.max(0, start), 
        end: Math.min(tableData.length, end) 
      });
    };

    const container = tableContainerRef.current;
    container?.addEventListener('scroll', handleScroll);
    return () => container?.removeEventListener('scroll', handleScroll);
  }, [tableData.length]);

  const memoizedHeaders = useMemo(() => tableHeaders, [tableHeaders]);
  const visibleData = useMemo(() => tableData.slice(visibleRange.start, visibleRange.end), [tableData, visibleRange]);

  return(
    <div style={{ height: '600px', overflow: 'auto' }} ref={tableContainerRef}>
      <table>
        <thead>
          <tr>
            {memoizedHeaders.map((item, index) => <TableHeader key={index} item={item} index={index} />)}
          </tr>
        </thead>
        <tbody>
          {/* 渲染空白行占位,保证滚动条高度正确 */}
          {Array.from({ length: visibleRange.start }).map((_, i) => (
            <tr key={`placeholder-${i}`} style={{ height: `${rowHeight}px`, visibility: 'hidden' }} />
          ))}
          {visibleData.map((item, index) => (
            <TableRow key={item.id || visibleRange.start + index} item={item} index={visibleRange.start + index} tableHeaders={memoizedHeaders} />
          ))}
        </tbody>
      </table>
    </div>
  )
}

4. 其他细节优化

  • 给列表项用唯一ID当key(不要只用index),减少React diff时的性能浪费
  • 提前处理好需要渲染的数据,避免在map循环里执行复杂计算,用useMemo缓存处理结果

内容的提问来源于stack exchange,提问作者cbish77

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 08:05:02