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

React Context 更新异常:更新UtilContext触发UserContext订阅组件重复渲染

问题根源分析

你遇到的问题核心原因是React Context的Provider在父组件重渲染时,会创建新的value对象引用——哪怕value里的实际数据没变化,只要引用变了,订阅该Context的组件就会触发更新。

具体到你的代码场景:

  • 滚动页面触发setShowNav或setShowToTop时,MyApp组件会重新渲染。
  • 每次MyApp渲染时,UserContext.Provider的value都会生成一个全新的对象:{ data: user, setUser: updateUser }。虽然user和updateUser本身可能没变化,但这个对象的引用每次都不一样。
  • 你的Component组件订阅了UserContext,且useEffect依赖了user(这里的user是Context返回的对象),所以每次Context的value引用更新,useEffect就会被触发,重复打印"User changed.."。

解决方案:用useMemo缓存Context的value对象

我们需要让UserContext.Provider的value只有在user真正变化时才更新引用,这可以通过useMemo来实现:

修改_app.js中的相关代码:

import { useState, useEffect, useMemo } from "react"; // 新增useMemo导入

// ... 其他代码保持不变 ...

function MyApp({ Component, pageProps }) {
  // ... 其他状态和逻辑不变 ...

  // 用useMemo缓存UserContext的value,仅当user变化时才重新创建对象
  const userContextValue = useMemo(() => ({
    data: user,
    setUser: updateUser
  }), [user]); 

  return (
    <>
      <ChakraProvider>
        <ReduxProvider store={store}>
          {/* 使用缓存后的value */}
          <UserContext.Provider value={userContextValue}>
            <UtilContext.Provider value={{ showNav, showToTop }}>
              <Component {...pageProps} />
            </UtilContext.Provider>
          </UserContext.Provider>
        </ReduxProvider>
      </ChakraProvider>
    </>
  );
}

同理,为了避免UtilContext后续出现类似问题,也可以给它的value做同样的缓存优化:

const utilContextValue = useMemo(() => ({
  showNav,
  showToTop
}), [showNav, showToTop]);

// 替换为缓存后的value
<UtilContext.Provider value={utilContextValue}>

额外优化:用useCallback包裹滚动事件处理函数

你的window.onscroll回调函数每次MyApp渲染时都会重新创建,虽然不直接导致当前问题,但可以用useCallback包裹,减少不必要的函数创建:

useEffect(() => {
  const user = getLocalStorage("user");
  if (user) {
    setUser(JSON.parse(user));
  }

  // 用useCallback包裹滚动处理函数
  const handleScroll = () => {
    let currentScrollPos = window.pageYOffset;
    if (prevScrollpos > currentScrollPos) {
      setShowNav(true);
    } else {
      setShowNav(false);
    }
    prevScrollpos = currentScrollPos;
    if (
      document.body.scrollTop > 20 ||
      document.documentElement.scrollTop > 20
    ) {
      setShowToTop(true);
    } else {
      setShowNav(true);
      setShowToTop(false);
    }
  };

  window.onscroll = handleScroll;

  // 记得清理事件监听,避免内存泄漏
  return () => {
    window.onscroll = null;
  };
}, []);

修改完成后,只有当user真正发生变化时,UserContext的value才会更新,Component里的useEffect才会触发,滚动页面时就不会再重复打印日志了。

内容的提问来源于stack exchange,提问作者Rahul

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.01 01:17:38