使用带有 React 的 Context API 的 react-router-dom 的意外输出

Posted

技术标签:

【中文标题】使用带有 React 的 Context API 的 react-router-dom 的意外输出【英文标题】:Unexpected output using react-router-dom with React's Context API 【发布时间】:2021-06-08 12:09:40 【问题描述】:

对于我的一个小项目,我正在尝试尽可能实现最基本的身份验证,使用没有 Redux 的 React 上下文 API。

import  createContext, useContext, useState  from 'react'

export const AuthContext = createContext()

export const useAuth = () => 
    const context = useContext(AuthContext)

    if(context === null) throw new Error('Nope')

    return context  


export const AuthProvider = (props) => 
    const [authenticated, setAuthenticated] = useState(false)

    const login = () => 
        setAuthenticated(true)

        localStorage.setItem(storageKey, true)
    
    
    const logout = () => 
        setAuthenticated(false)

        localStorage.setItem(storageKey, false)
    
    
    return <AuthContext.Provider value=authenticated, login, logout ...props/>


export default AuthContext

我创建了一个上下文,并像这样将我的&lt;App /&gt; 组件包裹在其中; &lt;AuthProvider&gt;&lt;/App&gt;&lt;/AuthProvider&gt;。因为我想保持认证状态,所以我使用浏览器的本地存储来存储一个简单的布尔值。

import PrivateRoute from './PrivateRoute'
import  useAuth  from './context/AuthContext'

import  AuthPage  from './pages'

import 
    BrowserRouter,
    Switch,
    Route,
 from 'react-router-dom'

import  useEffect  from 'react'

const App = () => 
    const  login, authenticated  = useAuth()

    useEffect(() => 
        const token = localStorage.getItem('user')

        if(token && token !== false)  login() 
    )

    return (
        <BrowserRouter>
            <Switch>
                <PrivateRoute exact path="/auth" component=AuthPage />
                <Route exact path='/'>
                    Dashboard
                </Route>
            </Switch>
        </BrowserRouter>
    )


export default App

然后,在我的&lt;App /&gt; 组件中,我尝试调用从AuthProvider 给出的login 回调,这让我认为这让我在页面刷新期间登录。当我尝试访问当前组件中的authenticated 变量时,它确实有效。它表明我已通过身份验证。

但是,当我尝试设置 PrivateRoute 时,只有经过身份验证的用户才能这样访问:

import 
    Route,
    Redirect
 from 'react-router-dom'

import  useAuth  from './context/AuthContext'

const PrivateRoute = ( component: Component, ...rest ) => 
    const  authenticated  = useAuth()
    
    if(authenticated) 
        return <Route ...rest render=(props) => <Component ...props /> />
    

    return <Redirect to= pathname: '/login'  />


export default PrivateRoute

它不起作用。它只是将我重定向到登录页面。这是怎么来的? PrivateRoute 组件正在从 &lt;App /&gt; 组件呈现。还有,这个问题有什么解决办法?

【问题讨论】:

useEffect 被调用 after PrivateRoute 组件已经挂载,当它调用它又调用login 函数时,用户已经被重定向到@ 987654336@路线。 PrivateRoute 组件内,仅在调用login 函数后检查authenticated 的值。创建了一个demo,向您展示如何解决问题。 (尝试更改 useEffect 挂钩内的 token 变量的值) 上面的这个演示很棒。似乎最好只跟上身份验证的加载状态,这允许组件呈现,但您可以使用加载状态基本上等待身份验证发生。 【参考方案1】:

与其在每次重新渲染时运行useEffect 来检查用户是否应该登录,不如使用localStorage 中的值初始化authenticated 状态:

const storageKey = 'user'
const initialState = JSON.parse(localStorage.getItem(storageKey)) ?? false

export const AuthProvider = (props) => 
  const [authenticated, setAuthenticated] = useState(initialState)

  const login = () => 
      setAuthenticated(true)

      localStorage.setItem(storageKey, true)
  
  
  const logout = () => 
      setAuthenticated(false)

      localStorage.setItem(storageKey, false)
  
  
  return <AuthContext.Provider value=authenticated, login, logout ...props/>

【讨论】:

【参考方案2】:

感谢Yousaf在cmets和GitHub上HospitalRun项目中的解释,我在&lt;App /&gt;组件中做了一个加载状态。

import  useAuth  from './context/AuthContext'
import  useEffect, useState  from 'react'

import Router from './Router'

const App = () => 
    const [ loading, setLoading ] = useState(true)
    const  login  = useAuth()

    const token = localStorage.getItem('user')

    useEffect(() => 
        if(token && token !== false)  
            login()
        

        setLoading(false)
    , [loading, token, login])

    if (loading) return null

    return <Router />


export default App

这里我只让任何东西渲染,在login函数被调用之后。

if (loading) return null

如果这可以做得更好,仍然会收到反馈!

【讨论】:

我会说loading 是一个冗余状态,因为它与authenticated 是相反的布尔值,对吧?我想您可以改为使用authenticated。似乎您的 useEffect 用于初始状态目的,因为每次后续执行都来自 login|out 在其他地方被调用。如果您按照我的建议尝试使用 localStorage 值(这是一种常用方法)初始化您的状态,我相信如果我没记错的话,您只能直接检查authenticated

以上是关于使用带有 React 的 Context API 的 react-router-dom 的意外输出的主要内容,如果未能解决你的问题,请参考以下文章

NextJS 使用带有布局组件包装器的 React 的 Context API

React Context API 很慢

React Context API,Hook 加载两次。一次没有数据,一次有数据

从 REST API 填充上下文并在带有 useEffect 和 useContext 的 React 组件中使用它

React - 新的 Context API 不适用于 Class.contextType,但适用于 Context.Consumer

React API