为什么React会重渲染未改动的列表项?如何避免这类不必要的重渲染?
根因定位
全量列表重渲染和key的设置、setArr的数组更新逻辑没有关系,核心问题是memo包裹的列表项组件被定义在了App组件的函数内部。
- 每次
App触发状态更新(比如点击按钮调用setArr),整个App函数都会完整执行一遍。 - 写在App函数体内的
const SimpleComponent = memo(...)代码,每次执行都会生成一个全新的、引用完全不同的组件对象。 - React的组件diff逻辑第一优先级就是对比组件类型的引用是否一致:只要同位置的组件类型引用变了,不管props内容是不是和之前完全一样,React都会直接卸载旧组件、挂载全新组件实例,根本走不到
memo的props对比判断环节。 - 这就是为什么哪怕key设置完全符合规范、传入的
textprop没有任何变化,原有a/b/c三个列表项也会被全量重新渲染——在React的视角里,这些组件已经是和上次渲染时完全不同的新组件了,和后续追加的d/e/f没有任何区别。
逻辑验证
你可以在代码里给组件对象打引用日志直观看到问题:
- 第一次App渲染时,SimpleComponent是第一次生成的引用S1
- 点击按钮触发App重渲染后,SimpleComponent变成了全新的引用S2
- 因为S1和S2不是同一个引用,React会把所有挂载了S1的旧节点全部销毁,换成S2的新节点,自然所有节点都会触发渲染逻辑。
修复方案
把SimpleComponent的定义移到App组件的外部作用域,保证这个组件的引用在整个应用生命周期内是稳定的,不会随着App的重渲染被重新创建。
修正后的核心代码如下:
// 组件移到App外部,引用全局稳定 const SimpleComponent = memo(({ text }) => { console.log("Rendered ", text); return <li>{text}</li>; }); export default function App() { const [arr, setArr] = useState(["a", "b", "c"]); const addItem = () => { const extraItems = ["d", "e", "f"]; setArr((arr) => [...arr, ...extraItems]); }; return ( <div className="App"> <ul> {arr.map((item) => <SimpleComponent key={item} text={item} />)} </ul> <button onClick={() => addItem()}>Add more</button> </div> ); }
修复后点击追加按钮,控制台只会打印d/e/f三个新增项的渲染日志,原有a/b/c三个组件会被React直接跳过渲染,完全符合预期。
认知澄清
- key的节点复用能力,生效的前提是渲染的组件类型引用是稳定的。如果组件本身每次渲染都是新生成的,哪怕key设置得再规范,也做不到节点复用。
- 不要把组件定义在父组件的函数内部,这种写法本身就会破坏React的组件复用逻辑,哪怕套上多层memo也抵消不了组件引用不稳定带来的全量重渲染问题。
- 你之前给
memo传入的第二个参数true,本质是要求传入的所有props必须全等才跳过渲染,对于单字符串prop的场景,memo默认的浅比较已经足够生效,不需要自定义严格对比规则。
内容的提问来源于stack exchange,提问作者Ben Jackson
相关产品推荐
相关产品推荐

