You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React中useMemo缓存动态组件后重渲染仍执行useState初始逻辑的原因

问题根因拆解

核心逻辑分层

你观察到的每次App强制更新都触发getData called的现象,是两个非常常见的React使用误区叠加导致的,和useMemo的缓存失效、useState的执行时机直接相关:

  • 关于useState的执行时机误区
    很多人以为传给useState的初始值只会在组件首次挂载时执行一次,这其实是对JS求值规则的误解:

    当你写useState(getData())时,getData()这段代码的执行时机,是Comp组件函数体执行到这一行语句的瞬间。只要Comp没有被React卸载、没有因为组件引用变化被判定为需要重新挂载,那么Comp的函数体只会在首次渲染时执行到这行代码,后续重渲染时哪怕逻辑上走到useState调用,也不会重新触发getData的执行。

  • 关于useMemo的作用边界误解
    useMemo的本质只是在依赖值不变时,缓存上一次的计算结果,它从来不是用来“永久保持组件引用稳定”或者“阻止组件重渲染”的万能工具:
    1. 如果useMemo的依赖数组里包含了每次重渲染都会变化的非稳定值(比如内联对象、内联数组、未做缓存的函数),那么useMemo每次都会重新执行工厂逻辑,生成一个全新的Comp组件引用。
    2. React在做组件树diff的时候,只要发现同一渲染位置的组件引用和上一次不一致,就会直接卸载旧的组件实例,挂载全新的组件实例。这个时候Comp的整个函数体会从头执行,自然就会跑到useState(getData())的代码行,触发控制台的日志打印。
    3. 就算useMemo的依赖完全稳定、Comp引用没有变化,只要你的RenderWidget组件本身没有被React.memo包裹,App触发强制更新时RenderWidget的函数体一定会重新执行——这种场景下因为Comp引用稳定,React不会重复挂载Comp,也就不会触发getData的打印。

最常见的踩坑写法

你现在遇到的问题,大概率是下面的错误写法导致的:

function RenderWidget() {
  // 注意:这个配置对象每次组件渲染都是全新的引用
  const widgetConfig = { id: "test-widget" };
  // useMemo依赖了不稳定的配置对象,缓存完全失效
  const Comp = useMemo(() => {
    return () => {
      const [data, setData] = useState(getData());
      return <div>{data}</div>
    }
    // 因为widgetConfig每次都是新引用,useMemo永远认为依赖变了
  }, [widgetConfig]);

  return <Comp />;
}

这种写法下,每次App触发更新、RenderWidget重新执行的时候,widgetConfig都是新的对象引用,useMemo每次都会重新创建一个全新的Comp组件,React自然会反复卸载、重挂Comp,导致getData被反复调用。

快速验证方法

你可以在Comp组件内部加一个空依赖的挂载生命周期日志:

useEffect(() => {
  console.log("Comp mounted");
}, [])

如果每次点击H1都能看到Comp mounted的日志,就可以100%确认是Comp组件引用不稳定、被反复重挂导致的问题,和useState本身的机制无关。

内容的提问来源于stack exchange,提问作者Adrián

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.24 16:45:04