如何优化React大型列表渲染?已用memo/useCallback仍卡顿的解决方法
优化React大型列表渲染的方案
针对你的问题,先从修复当前重渲染问题入手,再解决大型列表的DOM性能问题,具体方案如下:
1. 修正React.memo的使用方式
你当前在map循环内每次渲染都调用React.memo,这会导致每次List组件重渲染时都生成新的memoized组件实例,完全起不到缓存作用。正确的做法是直接将Item组件本身用React.memo包裹:
// 直接包裹Item组件,而非在map循环内使用 const Item = React.memo(({ item, onUpdate }) => { // ... 原Item组件内容 });
2. 用useCallback缓存更新函数,避免不必要的prop变化
List组件中的handleUpdate函数每次重渲染都会重新创建,导致Item组件的onUpdate prop始终变化,即使使用了React.memo也会触发重渲染。这里可以通过useCallback缓存函数,同时改用setData的函数式更新消除对data的依赖:
import React, { useState, useCallback } from "react"; const List = ({ items }) => { const [data, setData] = useState(items); // 用useCallback缓存函数,依赖数组为空(因使用函数式更新) const handleUpdate = useCallback((updatedItem) => { setData(prevData => { const index = prevData.findIndex(i => i.id === updatedItem.id); const newData = [...prevData]; newData[index] = updatedItem; return newData; }); }, []); return ( <div className="list"> {data.map((item) => ( <Item key={item.id} item={item} onUpdate={handleUpdate} /> ))} </div> ); };
3. 优化Item组件的状态同步
你当前在Item组件内部维护了data状态,但未同步props.item的变化(比如其他操作导致列表数据更新时)。可以添加useEffect来同步props,避免数据不一致:
const Item = React.memo(({ item, onUpdate }) => { const [data, setData] = useState(item); // 当props.item变化时,同步到本地state useEffect(() => { setData(item); }, [item]); const handleChange = (e) => { setData(prev => ({ ...prev, value: e.target.value })); }; const handleClick = () => { onUpdate(data); }; return ( <div className="item"> <p>{data.name}</p> <input type="text" value={data.value} onChange={handleChange} /> <button onClick={handleClick}>Update</button> </div> ); });
4. 虚拟滚动:解决1000条数据的DOM性能问题
即使解决了重渲染,1000个DOM节点同时存在于页面中依然会导致布局和渲染性能问题。虚拟滚动只渲染当前视口内的列表项,大幅减少DOM数量,是大型列表的必备优化方案。
以react-window为例,实现方式如下:
首先安装依赖:
npm install react-window
然后修改List组件:
import { FixedSizeList as List } from 'react-window'; // 定义每个列表项的高度 const ITEM_HEIGHT = 80; const VirtualList = ({ items, handleUpdate }) => { const Row = ({ index, style }) => { const item = items[index]; return ( <div style={style}> <Item key={item.id} item={item} onUpdate={handleUpdate} /> </div> ); }; return ( <List height={600} // 列表容器高度 itemCount={items.length} itemSize={ITEM_HEIGHT} width="100%" > {Row} </List> ); }; // 原List组件修改为使用虚拟滚动 const ListContainer = ({ items }) => { const [data, setData] = useState(items); const handleUpdate = useCallback((updatedItem) => { setData(prevData => { const index = prevData.findIndex(i => i.id === updatedItem.id); const newData = [...prevData]; newData[index] = updatedItem; return newData; }); }, []); return <VirtualList items={data} handleUpdate={handleUpdate} />; };
额外优化建议
- 精简props传递:如果Item组件不需要
item的所有字段,只传递所需的id、name、value等字段,减少props对比的开销。 - 用useMemo缓存复杂计算:如果Item组件中有复杂的计算逻辑,用
useMemo缓存计算结果,避免每次渲染重复计算。
内容的提问来源于stack exchange,提问作者Saeed Mansoori
相关产品推荐
相关产品推荐

