React 路由守卫深度解析:打造安全的前端路由体系

240 阅读6分钟

引言

在构建复杂的 React 应用时,我们常常需要对某些页面进行权限校验,比如支付页面、用户中心等。这些页面必须在用户登录后才能访问。这时,路由守卫(Route Guard) 就派上了用场。

路由守卫

路由守卫是一种在用户访问某个路由前进行权限判断的机制。如果用户未登录或权限不足,则阻止访问并重定向到登录页或其他提示页面。
对于路由守卫当中首先我们需要在已写的App.jsx当中引入路由组件的内容

import ProtectRoute from './pages/ProtectRoute'

何时需要路由守卫

何时要校验我们的权限呢,也就是说在我们下单的时候,点赞的时候是需要登录才能进行的操作的。这时候如果有校验权限的路由出现就可以很好的解决这个问题。
让我们来添加一个pay的页面吧,也就是添加Route组件页面。

const Pay = lazy(()=>import('./pages/Pay'))

<Route path="pay" element={
  <ProtectRoute>
     <Pay/>
  </ProtectRoute>
}/>

pay.gif 当我们跳转到pay页面的时候可以看到这时候页面并不会直接跳转到Pay组件页面,而是会先经过路由守卫组件。

完善路由守卫组件(鉴权组件)

一个组件当中包着一个组件被称作什么呢? 这里叫props的Children属性,让我们看看props里面的内容吧!

// 鉴权组件
const ProtectRoute = (props) => {
  // console.log(props);
  // 并非子组件 
  // children属性 提升定制性
  const { children } = props;

  return (
    <>
      {children}
    </>
  )
}

export default ProtectRoute

image.png

判断是否登录成功

当我们要支付的时候就需要弹出登录界面框,这时候就需要判断用户输入的内容是否正确,判断之后分别跳转进入成功的页面和失败的页面。在这里需要使用到Navigate组件,实现页面的重定向(跳转),当成功跳转之后,便进入到pay支付页面啦。

import{
  Navigate
} from 'react-router-dom'

const isLogin = localStorage.getItem('isLogin') === 'true';
if(!isLogin){
    return <Navigate to="/login" />
}
return children

login.gif

登陆页面设计

1.首先设计一个表单

这里对很多小伙伴来说登录表单的代码实在太简单了,为此咱们就放一段普遍的登录表单代码上去。

return (
    <form onSubmit={handleSubmit}>
      <h1>登录界面</h1>
      <input
        type="text"
        placeholder="请输入用户名"
        required
        value={username}
        onChange={(event) => setUsername(event.target.value)}
      />
      <input
        type="password"
        placeholder="请输入密码"
        required
        value={password}
        onChange={(event) => setPassword(event.target.value)}
      />
      <button type="submit">登录</button>
    </form>
  )

登录表单这块并不是要学习的重点,重点则在于如何写登录表单的逻辑。

const s
const handleSubmit = (event) =>{
    event.preventDefault();
    if(username === 'Happy' && password === '1314520'){
        localStorage.setItem('isLogin', 'true');
    }else {
        alert('用户名或密码错误');
    }
}

2.useLocation Hook函数

现在需要从登录页面发送请求,实现从哪里来回到哪里去,这时候我们就需要用到useLocation这个Hook函数了。

📌 基本作用:

useLocation() 返回当前页面的 location 对象,可以用来:

  • 获取当前路径(pathname)
  • 获取 URL 中的查询参数(search)
  • 获取 hash 部分
  • 获取通过编程导航时传递的状态(state)
实战代码
import{
    useLocation
} from 'react-router-dom'

const {pathname} = useLocation(); // 获取当前路径
if(!isLogin){
    return <Navigate to="/login" state={{ from: pathname }}/>
}
state={{ from: pathname }}作用

相信很多小伙伴看到这里都是对这个代码一脸懵B,没错小编看到这里的时候也一时转不过弯来。但我们去问下ai便可以知道这段代码的作用: 传递跳转时的状态信息,不显示在 URL 中,但可以在目标页面中通过 useLocation() 获取

3.回到Login中

import{
    useNavigate, // Navigate 组件 js 跳转
    useLocation
} from 'react-router-dom'

const location = useLocation();
console.log(location)

image.png 打印Login当中location当中的内容,可以看到在state当中会有咱们之前传过来的值。这时候就需要拿到state当中的值,并且通过useNavigate进行页面跳转。让我们来看看下述代码:

const location = useLocation();
const navigate = useNavigate();
const handleSubmit = (event) =>{
    event.preventDefault();
    if(username === 'Happy' && password === '1314520'){
        localStorage.setItem('isLogin','true');
        navigate(location.state.from || '/');
    }else{
        alert('用户名或密码错误');
    }
}

在这里有个难点,如果左侧值为假(如 undefinednull 或空字符串),则使用默认值 /,确保跳转路径始终有效。在这里还可以对代码进行安全性能上的优化:

navigate(location.state.from || '/');

使用 可选链操作符(?. ,可以安全访问嵌套属性,避免在 locationlocation.stateundefined / null 时抛出错误。

navigate(location?.state?.from || '/');
代码是否安全访问?说明
location.state.from❌ 不安全如果 location 或 location.state 不存在,会抛出 Cannot read property 'state' of undefined 等错误
location?.state?.from✅ 安全如果任何一级为 null 或 undefined,表达式会直接返回 undefined 而不是报错

4. 页面效果展示

ProtectRouter.gif 可以看到我们通过使用ProtectRoute组件和Login组件实现了应用当中,重要的页面应用保护,对一些重要操作页面保留了其该有的安全性。

路由代码汇总

App.jsx根组件当中:

import {
  useState,
  lazy,
  Suspense
} from 'react'
import './App.css'
import {
  BrowserRouter as Router,
  Routes,
  Route
} from 'react-router-dom'
import Navigation from './components/Navigation'
import ProtectRoute from './pages/ProtectRoute'
// 函数 路由 -> Route 
// 懒加载
const Home = lazy(() => import('./pages/Home'))
const About = lazy(() => import('./pages/About'))
const Pay = lazy(() => import('./pages/Pay'))
const NotFound = lazy(() => import('./pages/NotFound'))
const Login = lazy(() => import('./pages/Login'))
// import Home from './pages/Home'
// import About from './pages/About'

function App() {

  return (
    <>
      <Router>
        <Navigation />
        <Suspense fallback={<div>Loading...</div>}>
          <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/about" element={<About />} />
            <Route path="/login" element={<Login />} />
            {/* 鉴权 */}
            <Route path="/pay" element={
              <ProtectRoute>
                <Pay />
              </ProtectRoute>
            } />
            <Route path="*" element={<NotFound />} />
          </Routes>
        </Suspense>
      </Router>
    </>
  )
}

export default App

ProtectRoute组件当中:

import {
  Navigate,
  useLocation
} from 'react-router-dom'

// 鉴权组件
const ProtectRoute = (props) => {
  // console.log(props);
  // 并非子组件
  // children属性 提升定制性
  const { children } = props;
  const pathname = useLocation();
  const isLogin = localStorage.getItem('isLogin') === 'true';

  if (!isLogin) {
    return <Navigate to="/login" state={{ from: pathname }} />
  }

  return children
}

export default ProtectRoute

Login组件当中:

import {
  useState
} from 'react'
import {
  useNavigate, // Navigate 组件 js 跳转
  useLocation
} from 'react-router-dom'

const Login = () => {
  const location = useLocation();
  const navigate = useNavigate();
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const handleSubmit = (event) => {
    event.preventDefault();
    if (username === 'Happy' && password === '1314520') {
      localStorage.setItem('isLogin', 'true');
      navigate(location?.state?.from || '/');
    } else {
      alert('用户名或密码错误');
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <h1>登录界面</h1>
      <input
        type="text"
        placeholder="请输入用户名"
        required
        value={username}
        onChange={(event) => setUsername(event.target.value)}
      />
      <br />
      <input
        type="password"
        placeholder="请输入密码"
        required
        value={password}
        onChange={(event) => setPassword(event.target.value)}
      />
      <br />
      <button type="submit">登录</button>
    </form>
  )

}
export default Login

导航栏Navigation当中:

import { Link } from 'react-router-dom'


const Navigation = () => {
  return (
    <nav>
      <ul>
        <li><Link to="/">Home</Link></li>
        <li><Link to="/about">About</Link></li>
        <li><Link to="/login">Login</Link></li>
      </ul>
    </nav>
  )
}

export default Navigation;

其它页面的风格都是大差不差,简陋的很,我们就拿pay这个页面代码来举例就是:

import './index.css';
const Pay = () => {
  return (
    <div className="pay">
      Pay
    </div>
  )
}

export default Pay

总结

路由守卫与登录跳转:打造流畅可控的前端导航

  1. 守卫核心:  ProtectRoute 组件精准拦截,为敏感路由筑起权限屏障。
  2. 智能导航:  利用 Navigate 与 useLocation,实现登录状态下的无缝跳转(重定向至原目标页或默认页)。
  3. 状态传递:  state={{ from: pathname }} 巧妙记录来源,确保登录后精准“归位”。

上面就是React路由守卫深度理解的全部内容了,如果大家对路由的基本使用还不清楚的话,可以看看我的上两篇文章哦!
React 路由懒加载详解:优化你的前端性能
适合小白初识React-Router的一篇文章