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
相关产品推荐
相关产品推荐

