如何避免列表整体重渲染?React应用优化重构咨询
优化React列表重渲染问题的方案
先拆解原代码里导致不必要重渲染的核心问题,再一步步重构优化:
原代码的核心问题
- 状态更新直接修改原对象:
handleUpdate里直接修改prevList[0].value,属于违反React状态不可变原则的写法,虽然后续返回了新数组,但这种操作可能引发逻辑隐患。 - 列表key使用不稳定值:用随机生成的
value作为key,每次更新第一行时key都会变化,触发组件卸载+重新挂载,完全违背React的diff优化逻辑。 - Memo比较逻辑无效:自定义的memo比较函数依赖
children.props.children,但React元素是引用类型,父组件每次渲染都会生成新的元素实例,这个比较逻辑根本不生效,导致所有列表项都会重渲染。 - 组件接收冗余props:Row组件定义了
label和valueprops,但实际未使用,父组件也未传递,属于无效代码。
重构优化步骤
1. 修复状态更新的不可变性
修改handleUpdate,创建新的对象而非直接修改原数组元素:
const handleUpdate = useCallback(() => { setList((prevList) => { // 创建新的第一行对象,不修改原数组元素 const newFirstItem = { ...prevList[0], value: Math.random() }; // 替换原数组第一个元素,返回全新数组 return [newFirstItem, ...prevList.slice(1)]; }); }, []);
2. 设置稳定的列表key
用label(或固定的index,因列表长度静态不变)作为key,保证key的稳定性:
{ list.map((item) => ( <MRow key={item.label}> <b>{item.value}</b> </MRow> )) }
3. 正确使用memo优化组件
简化Row组件的props,让memo的默认浅比较生效,同时给Button也加上memo(可选,避免父组件渲染时Button的无意义重渲染):
// 优化Button组件 const Button = memo(function Button(props) { const { children, onClick } = props; return <button onClick={onClick}>{children}</button>; }); // 简化Row组件,只接收必要的children function Row({ children }) { return <div style={{ marginTop: '8px' }}>{children}</div>; } // 使用默认的memo浅比较即可,无需自定义逻辑 const MRow = memo(Row);
4. 完整优化后的代码
import ReactDOM from 'react-dom/client'; import React, { useState, useCallback, memo } from "react"; const initList = () => { return Array.from({ length: 10 }, (_el, index) => ({ value: Math.random(), label: `row ${index + 1}` })); }; function App() { const [list, setList] = useState(initList()); const handleUpdate = useCallback(() => { setList((prevList) => { const newFirstItem = { ...prevList[0], value: Math.random() }; return [newFirstItem, ...prevList.slice(1)]; }); }, []); return ( <div> <h1>List App</h1> <Button onClick={handleUpdate}>Update "row 1"</Button> {list.map((item) => ( <MRow key={item.label}> <b>{item.value}</b> </MRow> ))} </div> ); } const Button = memo(function Button(props) { const { children, onClick } = props; return <button onClick={onClick}>{children}</button>; }); function Row({ children }) { return <div style={{ marginTop: '8px' }}>{children}</div>; } const MRow = memo(Row); ReactDOM.createRoot(document.querySelector('#root')).render(<App />);
验证效果
点击按钮后,仅App、Button和第一行的MRow会触发渲染,其余9行不会重渲染,完全消除了不必要的性能消耗。
内容的提问来源于stack exchange,提问作者Pavel
相关产品推荐
相关产品推荐

