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
相关产品推荐
相关产品推荐

