使用带有 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
我创建了一个上下文,并像这样将我的<App />
组件包裹在其中; <AuthProvider></App></AuthProvider>
。因为我想保持认证状态,所以我使用浏览器的本地存储来存储一个简单的布尔值。
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
然后,在我的<App />
组件中,我尝试调用从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
组件正在从 <App />
组件呈现。还有,这个问题有什么解决办法?
【问题讨论】:
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项目中的解释,我在<App />
组件中做了一个加载状态。
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,Hook 加载两次。一次没有数据,一次有数据
从 REST API 填充上下文并在带有 useEffect 和 useContext 的 React 组件中使用它
React - 新的 Context API 不适用于 Class.contextType,但适用于 Context.Consumer