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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.07 12:18:01