React Router V6 官方路由身份验证示例

4,927 阅读4分钟

此示例演示如何限制对经过身份验证的用户的路由访问。

reactrouter.com/docs/en/v6/…

请务必注意以下功能:

  • 使用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> 组件内进行路由拦截
  • 此时 AuthContextuser 属性为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>
  )
}

示例完成!祝好运!