React中配合Typescript使用ContextAPI传递设置state失效问题求解
问题核心原因
你在_app.tsx的MyApp组件顶层调用了useUIContext,但此时<UIProvider>还没有渲染(Provider写在MyApp的return内容里),所以你拿到的是创建Context时传入的默认值里的空更新函数,调用后自然不会触发状态变更。
修复步骤
1. 调整_app.tsx结构,将依赖Context的逻辑移到Provider内部
修改后的_app.tsx代码如下:
import '../styles/globals.scss' import '@fortawesome/fontawesome-svg-core/styles.css' import { Provider } from 'react-redux' import { ThemeProvider } from '@material-ui/core' import { useStore }from '../store' import type { AppProps } from 'next/app' import theme from '../styles/theme' import { AnimatePresence, motion } from 'framer-motion' import { useRouter } from 'next/router' import { useEffect } from 'react' import NavBar from '../components/NavBar' import Footer from '../components/Footer' import MenuPanel from '../components/MenuPanel' import { Grid, useMediaQuery } from '@material-ui/core' import Head from 'next/head' import { UIProvider, useUIContext } from '../contexts/UIContext' // 抽离内部组件,放在UIProvider范围内调用Context function AppContent({ Component, pageProps, store }: { Component: any, pageProps: any, store: any }) { const isMobile = useMediaQuery('(max-width:600px)') const router = useRouter() const { menuPanelOpen, setMenuPanelOpen, navbarOpen, setNavbarOpen } = useUIContext() useEffect(() => { if ((global.window as any).Cypress) { global.window.store = store console.log("Cypress store is running and linked to global document") } },[store]) return( <ThemeProvider theme={theme}> <Grid container flexDirection="column"> <Grid container item flexWrap="nowrap"> { menuPanelOpen && <MenuPanel /> } <Grid container item flexDirection="column" sx={{minHeight: '100vh'}}> { navbarOpen && <NavBar isMobile={isMobile} /> } <AnimatePresence exitBeforeEnter> <Grid container item justifyContent="center" flexGrow={1} component={motion.div} key={router.pathname} initial={{opacity: 0, y: 20}} animate={{opacity: 1, y: 0}} exit={{opacity: 0, y: -20}}> <Component {...pageProps} /> </Grid> </AnimatePresence> <Footer /> </Grid> </Grid> </Grid> </ThemeProvider> ) } function MyApp({ Component, pageProps }: AppProps) { const store = useStore(pageProps.initialReduxState) return( <Provider store={store}> <Head> <meta content='width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no' name='viewport' /> </Head> <UIProvider> {/* 内部组件放在Provider内部,可正常获取Context值 */} <AppContent Component={Component} pageProps={pageProps} store={store} /> </UIProvider> </Provider> ) } export default MyApp
2. 新增页面离开时的状态重置逻辑
修改SignupTest组件的useEffect,添加清理函数,切页时自动重置菜单状态:
useEffect(() => { setMenuPanelOpen(false) // 清理函数,组件卸载/路由切换时执行 return () => setMenuPanelOpen(true) }, [setMenuPanelOpen])
如果需要验证状态是否更新,不要在调用setter后立即打印(state更新是异步的),可以单独加一个监听useEffect:
useEffect(() => { console.log("menuPanelOpen更新为:", menuPanelOpen) }, [menuPanelOpen])
可选优化
修改UIContext.tsx的默认值,将空更新函数改为抛错逻辑,后续如果再出现Context调用位置错误的情况会直接抛出明确异常,避免静默失败难以排查:
const defaultValues: UIContextType = { menuPanelOpen: true, setMenuPanelOpen: () => { throw new Error('useUIContext必须在UIProvider内部使用') }, navbarOpen: true, setNavbarOpen: () => { throw new Error('useUIContext必须在UIProvider内部使用') }, loggedIn: false }
内容的提问来源于stack exchange,提问作者Joel Hager
相关产品推荐
相关产品推荐

