此示例演示如何限制对经过身份验证的用户的路由访问。
请务必注意以下功能:
- 使用
useNavigate()
钩子和<Navigate>
组件在提交登录表单后进行命令式导航,以及在未经过身份验证的用户访问特定路线时进行声明式导航 - 使用
location.state
保留以前的位置,以便在用户进行身份验证后将其返回跳转到原来的URL位置 - 用于
navigate("...", { replace: true })
替换/login
历史堆栈中的路由,使用户在登录后,点击返回按钮时,不会重复返回登录页面
src/main.jsx
- 引入
BrowserRouter
import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter } from 'react-router-dom'
import App from './App'
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
)
src/views/Public.jsx
import React from 'react'
export default function Public() {
return (
<div>
<h3>Public 公共页</h3>
</div>
)
}
src/views/Protected.jsx
import React from 'react'
export default function Protected() {
return (
<div>
<h3>Protected 限制验证页</h3>
</div>
)
}
src/components/Layout.jsx
- 页面导航布局
<p>校验状态</p>
暂时写死校验状态Link to
到两个URL地址
import React from 'react'
import { Link, Outlet } from 'react-router-dom'
export default function Layout() {
return (
<div>
<p>校验状态</p>
<ul>
<li>
<Link to={'/'}>Public 公共页</Link>
</li>
<li>
<Link to={'/protected'}>Protected 限制验证页</Link>
</li>
</ul>
<Outlet />
</div>
)
}
src/App.jsx
- 引入路由
Route,Routes
- 引入
Layout Public
组件 - 编写路由映射关系,
Layout
布局组件无path
属性,直接穿透至下层子路由 - 默认根路由映射至
Public
组件页面
import { Route, Routes } from 'react-router-dom'
import Layout from './components/Layout'
import Protected from './views/Protected'
import Public from './views/Public'
function App() {
return (
<div>
<h2>React Router V6 官方路由身份验证示例</h2>
<Routes>
<Route element={<Layout />}>
<Route path='/' element={<Public />} />
<Route path='/protected' element={<Protected />} />
</Route>
</Routes>
</div>
)
}
export default App
此时访问 http://localhost:3000 点击两个链接可以导航到地址栏相关URL位置。
src/auth.js
- 模拟验证对象
const fakeAuthProvider = {
isAuthenticated: false,
signin(callback) {
fakeAuthProvider.isAuthenticated = true
setTimeout(callback, 100) // 模拟异步登录,执行回调函数
},
signout(callback) {
fakeAuthProvider.isAuthenticated = false
setTimeout(callback, 100) // 模拟异步退出,执行回调函数
},
}
export { fakeAuthProvider }
src/AuthProvider.jsx
- 拦截验证路由
import React, { useState } from 'react'
import { fakeAuthProvider } from './auth'
// 验证上下文空间
let AuthContext = React.createContext(null)
// 利用useContext导出验证上下文,供其它组件使用
export function useAuth() {
return React.useContext(AuthContext)
}
// 验证提供者
export default function AuthProvider({ children }) {
// 创建验证组件状态
let [user, setUser] = useState(null)
// 登录验证
let signin = (newUser, callback) => {
return fakeAuthProvider.signin(() => {
setUser(newUser)
callback()
})
}
// 退出登录
let signout = (callback) => {
return fakeAuthProvider.signout(() => {
setUser(null)
callback()
})
}
let value = { user, signin, signout }
// 传递验证上下文(AuthContext)属性给嵌套的插槽children子组件(App)
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
}
src/App.jsx
- 路由全局接收
<AuthProvider>
路由拦截组件属性
import { Route, Routes } from 'react-router-dom'
import AuthProvider from './AuthProvider'
import Layout from './components/Layout'
import Protected from './views/Protected'
import Public from './views/Public'
function App() {
return (
<AuthProvider>
<h2>React Router V6 官方路由身份验证示例</h2>
<Routes>
<Route element={<Layout />}>
<Route path='/' element={<Public />} />
<Route path='/protected' element={<Protected />} />
</Route>
</Routes>
</AuthProvider>
)
}
export default App
src/components/RequireAuth.jsx
- 请求验证路由拦截组件
- 未登陆的请求利用
<Navigate>
导航组件进行重定向
import React from 'react'
import { Navigate, useLocation } from 'react-router-dom'
import { useAuth } from '../AuthProvider' // 引入验证上下文
export default function RequireAuth({ children }) {
let auth = useAuth() // 获取验证对象
let location = useLocation() // 获取URL参数
if (!auth.user) {
// 未登入,使用Navigate组件重定向到登录页,传入state属性以保存当前URL位置信息
return <Navigate to='/login' state={{ from: location }} replace />
}
return children // 验证通过,返回插槽内容,例如: ProtectedPage 页面(显示页面)
}
src/views/Login.jsx
- 添加登陆组件
import React from 'react'
export default function Login() {
return <div>Login</div>
}
src/App.jsx
- 需要登录验证的路由,例如:
/protected
嵌套入<RequireAuth>
组件内进行路由拦截 - 此时
AuthContext
内user
属性为null,访问/protected
路由拦截后重定向至/login
import { Route, Routes } from 'react-router-dom'
import AuthProvider from './AuthProvider'
import Layout from './components/Layout'
import RequireAuth from './components/RequireAuth'
import Login from './views/Login'
import Protected from './views/Protected'
import Public from './views/Public'
function App() {
return (
<AuthProvider>
<h2>React Router V6 官方路由身份验证示例</h2>
<Routes>
<Route element={<Layout />}>
<Route path='/' element={<Public />} />
<Route
path='/protected'
element={
<RequireAuth>
<Protected />
</RequireAuth>
}
/>
<Route path='/login' element={<Login />} />
</Route>
</Routes>
</AuthProvider>
)
}
export default App
src/views/Login.jsx
- 编写Login登陆代码
- 利用
useLocation
获取URL路径参数 - 利用
useNavigate
勾子进行编程式路由导航
import React from 'react'
import { useLocation, useNavigate } from 'react-router-dom'
import { useAuth } from '../AuthProvider'
export default function LoginPage() {
let navigate = useNavigate()
let location = useLocation()
let auth = useAuth() // 导入验证上下文属性
// 获取URL来路,/ or /protected
let from = location.state?.from?.pathname || '/'
function handleSubmit(event) {
event.preventDefault() // 阻止元素发生默认的行为
let formData = new FormData(event.currentTarget) // 获取FormData对象
let username = formData.get('username').toString() // 获取username数据
// 模拟登陆验证,传递登录回调函数给 AuthProvider
auth.signin(username, () => {
navigate(from, { replace: true })
})
}
return (
<div>
<h3>必须登录才能访问路由地址: {from}</h3>
<form onSubmit={handleSubmit}>
<label>
用户名: <input type='text' name='username' />{' '}
<button type='submit'>Login</button>
</label>
</form>
</div>
)
}
src/components/AuthStatus.jsx
- 最后编写登陆验证状态代码
- 利用
AuthProvider
获取获取登陆状态 - 利用
useNavigate
勾子进行编程式路由导航
import React from 'react'
import { useNavigate } from 'react-router-dom'
import { useAuth } from '../AuthProvider'
export default function AuthStatus() {
let auth = useAuth()
let navigate = useNavigate()
if (!auth.user) {
return <p>您还没有登录.</p>
}
return (
<p>
欢迎 {auth.user}!{' '}
<button
onClick={() => {
auth.signout(() => navigate('/'))
}}
>
退出
</button>
</p>
)
}
src/components/Layout.jsx
Layout
布局组件挂载登陆状态组件<p>校验状态</p>
替换为<AuthStatus />
import React from 'react'
import { Link, Outlet } from 'react-router-dom'
import AuthStatus from './AuthStatus'
export default function Layout() {
return (
<div>
<AuthStatus />
<ul>
<li>
<Link to={'/'}>Public 公共页</Link>
</li>
<li>
<Link to={'/protected'}>Protected 限制验证页</Link>
</li>
</ul>
<Outlet />
</div>
)
}
示例完成!祝好运!