在 React 的上下文 API 中使用 react-router-dom 的意外输出
Unexpected output using react-router-dom with React's Context API
对于我的一个小项目,我正在尝试使用不带 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
来检查用户是否应该登录,不如使用 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}/>
}
感谢 GitHub 上的 Yousaf for the explaination in the comments and the 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
如果可以做得更好,我们仍然会收到反馈!
对于我的一个小项目,我正在尝试使用不带 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
来检查用户是否应该登录,不如使用 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}/>
}
感谢 GitHub 上的 Yousaf for the explaination in the comments and the 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
如果可以做得更好,我们仍然会收到反馈!