You need to enable JavaScript to run this app. 为什么React会重渲染未改动的列表项?如何避免这类不必要的重渲染?
优惠活动
大模型
产品
解决方案
定价
更多

为什么React会重渲染未改动的列表项?如何避免这类不必要的重渲染?

根因定位

全量列表重渲染和key的设置、setArr的数组更新逻辑没有关系,核心问题是memo包裹的列表项组件被定义在了App组件的函数内部。

  • 每次App触发状态更新(比如点击按钮调用setArr),整个App函数都会完整执行一遍。
  • 写在App函数体内的const SimpleComponent = memo(...)代码,每次执行都会生成一个全新的、引用完全不同的组件对象。
  • React的组件diff逻辑第一优先级就是对比组件类型的引用是否一致:只要同位置的组件类型引用变了,不管props内容是不是和之前完全一样,React都会直接卸载旧组件、挂载全新组件实例,根本走不到memo的props对比判断环节。
  • 这就是为什么哪怕key设置完全符合规范、传入的text prop没有任何变化,原有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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.31 14:36:10