【问题标题】:How to pass and set state with ContextAPI with Typescript in React?如何在 React 中使用带有 Typescript 的 ContextAPI 传递和设置状态?
【发布时间】:2021-08-19 01:00:53
【问题描述】:

我不确定自己做错了什么,因为我在 TS 之外使用了 ContextAPI,而且我认为我正确地实现了 TS。也许有人可以阐明我做错了什么。我没有收到任何错误,但是当我调用 setter 时,什么也没有发生。我希望能够通过使用此道具在某些页面的页面加载时动态打开和关闭面板,并通过将清理函数传递给 useEffect 来重置状态。如果有更好的方法,请告诉我。

谢谢!

我的 _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 { useCallback, useEffect, useRef, useState } from 'react'
import NavBar from '../components/NavBar'
import Footer from '../components/Footer'
import MenuPanel from '../components/MenuPanel'
import { useResizeDetector } from 'react-resize-detector/build/withPolyfill'
import { Grid, useMediaQuery } from '@material-ui/core'
import Head from 'next/head'
import { UIProvider, useUIContext } from '../contexts/UIContext'

function MyApp({ Component, pageProps }: AppProps) {
  const isMobile = useMediaQuery('(max-width:600px)')
  const router = useRouter()
  const store = useStore(pageProps.initialReduxState)
  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")
    }
  },[])

  return(
    <Provider store={store}>
      <Head>
        <meta content='width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no' name='viewport' />
      </Head>
      <UIProvider>
      <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>
      </UIProvider>
    </Provider>
  )
}
export default MyApp

我的 UIContext.tsx 文件:

import React, { createContext, FC, SetStateAction, useContext, useState } from 'react'

export type UIContextType = {
  menuPanelOpen: boolean
  setMenuPanelOpen: React.Dispatch<React.SetStateAction<boolean>>
  navbarOpen: boolean
  setNavbarOpen: React.Dispatch<React.SetStateAction<boolean>>
  loggedIn: boolean
}

const defaultValues: UIContextType = {
  menuPanelOpen: true,
  setMenuPanelOpen: (): void => {},
  navbarOpen: true,
  setNavbarOpen: (): void => {},
  loggedIn: false
}
export const UIContext = createContext<UIContextType>(defaultValues)

export function useUIContext() {
  return useContext(UIContext)
}

export const UIProvider: FC = ({children: children}) =>  {
  const [menuPanelOpen, setMenuPanelOpen] = useState(defaultValues.menuPanelOpen)
  const [navbarOpen, setNavbarOpen] = useState(defaultValues.navbarOpen)
  const [loggedIn, setLoggedIn] = useState(defaultValues.loggedIn)

  const value: UIContextType = {
    menuPanelOpen,
    setMenuPanelOpen,
    navbarOpen,
    setNavbarOpen,
    loggedIn
  }

  return (
    <UIContext.Provider value={value}>
      { children }
    </UIContext.Provider>
  )
}

使用setter的组件:

import { Grid, Typography } from '@material-ui/core'
import { makeStyles, createStyles } from '@material-ui/styles'
import LoginSignupForm from "../components/LoginSignupForm"
import { useEffect, useLayoutEffect } from 'react'
import { useUIContext } from '../contexts/UIContext'

const theme = makeStyles( theme =>
  createStyles({
    form: {

    },
    sidePane: {
      backgroundColor: "#333333",
      padding: '2rem',
      color: "#ffffff",

      '&::before': {
        content: '',
      }
    }
  })
) 

export default function SignupTest(props: any) {
  const { menuPanelOpen, setMenuPanelOpen, loggedIn } = useUIContext()
  const styles = theme()

  useEffect(() => {
    setMenuPanelOpen(false)
    console.log("menuPanelOpen is now ", menuPanelOpen)
  }, [])
  
  return (
    <Grid container item>
    <Grid container item xs={12} md={4} flexDirection="column" className={styles.sidePane}>
      <Grid item><Typography variant="h3"><b>TEST</b></Typography></Grid>
      <Grid container item flexDirection="column" justifyContent="center" flexGrow={1}><Typography variant="h4" textAlign="center"><b>TEST 1 2 3</b></Typography></Grid>
    </Grid>
    <Grid container item className={styles.form} xs={12} md={8} flexDirection="column" justifyContent="center" alignItems="center">
      <LoginSignupForm />
    </Grid>
  </Grid>
  )
}

【问题讨论】:

    标签: reactjs typescript react-state react-typescript react-context


    【解决方案1】:

    我的猜测是

    useUIContext()&lt;UIProvide /&gt; 处于同一级别
    所以在初始化的那一刻,它指的是一个不存在的分离上下文,
    而不是您要查找的 &lt;UIProvider /&gt; 中的 context

    function MyApp({ Component, pageProps }: AppProps) {
      const { menuPanelOpen, setMenuPanelOpen, navbarOpen, setNavbarOpen } = useUIContext()
    
      return(
          <UIProvider>
          <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>
          </UIProvider>
      )
    }
    export default MyApp
    

    所以这意味着你可能想做类似的事情

    function MyApp(props: AppProps) {
    
    return (
     <UIProvide>
        <AppWithoutProvider {...props}/>
     </UIProvide>
    )
    }
    
    function AppWithoutProvide({ Component, pageProps }: AppProps) {
    const { menuPanelOpen, setMenuPanelOpen, navbarOpen, setNavbarOpen } = useUIContext()
    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>
    )
    }
    

    格式很丑,我跳过了所有不相关的代码,但你应该能明白。

    【讨论】:

    • 它作为 被引入。
    • 所以我根本不包装 _app 而是像子组件一样从上下文中调用它?
    • @JoelHager 我认为您应该在 中调用 useContext。因为您在 的同一级别调用 useUIContext() ,所以它可能在初始化时成为一个单独的父上下文,所以它永远不是您要寻找的子上下文。你仍然包装你的 _app 但 initialize 并按照我的解决方案建议在 中真正调用 useContext
    • useUiContext 返回一个上下文
    • @JoelHager 上下文可能只是另一个具有默认值的上下文。出于调试目的,您可以将默认值传递为createContext({})。然后在提供者和useEffect(()=&gt;{}, [context]) 中调用 useContext 以检查上下文是否正在更改为您的第一个值。如果不是,那么您的代码有问题。
    猜你喜欢
    • 2020-12-04
    • 1970-01-01
    • 2023-03-05
    • 1970-01-01
    • 1970-01-01
    • 2020-05-16
    • 2022-08-21
    • 2021-10-18
    • 2017-06-16
    相关资源
    最近更新 更多