React 路由守卫:我用一个组件替代了 Vue 的 beforeEach

我是小ao,大二在读,正在用React + TypeScript做一个名为面试克星的 AI 面试准备平台。 完成用户系统闭环后,我遇到一个新问题: 如何保护首页、个人主页、练习页,确保只有登录用户才能访问?如果未登录,就自动跳转到欢迎页。 在之前的 Vue 论坛项目里,我用 Vue Router 的 beforeEach 全局钩子轻松搞定了这件事。到了 React,我发现 Router 根本没有 beforeEach。这意味着,我得换个思路。

1. 后端鉴权 vs 前端路由守卫

在做前端守卫之前,我已经用 JWT 中间件实现了后端鉴权。逻辑很清晰:

  1. 从请求头 Authorization 中取出 Token。
  2. 用 jwt.verify 验证 Token 有效性。
  3. 验证通过后,从数据库查出用户信息,挂载到 req.user 上。
  4. 后续路由通过 req.user 判断用户身份。

前端的路由守卫,本质上做的是同一件事,只是换了一个环境:

  1. 从 Zustand Store 中读取 isLogin 状态。
  2. 如果 isLogin 为 false,跳转到欢迎页。
  3. 如果 isLogin 为 true,正常渲染页面。

后端是"拦截请求",前端是"拦截页面渲染"。理解了这一点,剩下的就只是写代码了。

2. 用 ProtectedRoute 组件替代 beforeEach

React Router 没有 beforeEach,但它有一个更灵活的东西:组件。

我写了一个叫 ProtectedRoute 的组件,逻辑很简单:

tsx 复制代码
import React from 'react'
import { Navigate } from 'react-router-dom'
import useUserStore from '../store/useUserStore'

const ProtectedRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const isLogin = useUserStore((state) => state.isLogin)

  if (!isLogin) {
    return <Navigate to="/welcome" replace />
  }

  return <>{children}</>
}

export default ProtectedRoute

这个组件做的事本质上就是一个 if 判断:

  • 没登录 → 渲染 <Navigate> 组件,自动跳转到 /welcome。
  • 已登录 → 渲染 children,也就是正常的页面内容。

在 App.tsx 里使用时,只需要用它包裹需要保护的路由:

tsx 复制代码
<Route path="/" element={<ProtectedRoute><Home /></ProtectedRoute>} />
<Route path="/profile" element={<ProtectedRoute><Profile /></ProtectedRoute>} />
<Route path="/practice" element={<ProtectedRoute><Practice /></ProtectedRoute>} />

不需要保护的页面(欢迎页、登录页、注册页)保持原样即可。

为什么 React 没有 beforeEach?

后来我琢磨,这其实反映了 Vue 和 React 一个核心的思维差异:

  • Vue Router 把路由守卫当成一个独立的功能模块,用 beforeEach 这种全局钩子来处理。它像一道安检门,所有路由跳转都要先经过它。你只需要配置一次逻辑,它就自动生效,和你的页面组件是分离的。
  • React Router 认为"一切都是组件"。路由守卫也不例外。ProtectedRoute 就是一个普通的 React 组件,它接收 children,检查状态,然后决定渲染什么。

Vue 是配置式的全局守卫,React 是组件式的条件渲染。

两者没有优劣之分,只是设计哲学不同。理解了这个差异后,我突然觉得 React 的写法也挺优雅的------它把"要不要展示这个页面"这件事,变成了一个可复用的组件逻辑。

3. 导航栏改造:根据登录状态切换内容

做完路由守卫后,导航栏也需要跟着改造。之前的导航栏把所有链接都平铺展示出来了,不管用户有没有登录。现在需要根据 isLogin 状态,显示不同的导航项:

  • 已登录:首页、练习、个人主页
  • 未登录:欢迎页、登录、注册

改造的逻辑和 ProtectedRoute 一样,只是换成了条件渲染:

tsx 复制代码
const isLogin = useUserStore((state) => state.isLogin)

{isLogin ? (
  <>
    <NavLink to="/">首页</NavLink>
    <NavLink to="/practice">练习</NavLink>
    <NavLink to="/profile">个人主页</NavLink>
  </>
) : (
  <>
    <NavLink to="/welcome">欢迎页</NavLink>
    <NavLink to="/login">登录</NavLink>
    <NavLink to="/register">注册</NavLink>
  </>
)}

同时加上了 Logo,用绝对定位把导航链接居中,背景改成了渐变色。这些样式细节就不展开了,重点是条件渲染的逻辑。

4. 滚动条抖动:一个诡异的 Bug

导航栏改造完成后,我发现一个诡异的现象:点击"练习"时,导航栏会轻微抖动一下,但点击"首页"和"个人主页"不会。

我开始逐个排查:

  1. 检查了所有 CSS 过渡效果,去掉了 transition-all、hover:scale-105、font-bold 切换。
  2. 把所有可能引起布局变化的属性都移除了。
  3. 依然抖动。

然后我注意到了一个规律:只有"练习"页面会抖,其他页面都不抖。

这让我开始怀疑是页面内容长度的问题。检查了一下,练习页的内容确实比首页和个人主页更长。

根因找到了:浏览器的垂直滚动条。

当页面内容较短时(如首页),浏览器不显示滚动条。当页面内容较长时(如练习页),浏览器会在右侧自动显示一个滚动条。这个滚动条会占用大约 15-17 像素的宽度,把整个页面内容往左边挤一下。居中的导航栏也跟着往左移,肉眼看到的就是"抖了一下"。

解决方案 :在 index.css 中添加一行代码,强制滚动条始终存在:

css 复制代码
html {
  overflow-y: scroll;
}

这行代码的意思是:不管网页内容够不够长,都强制在浏览器窗口右侧显示垂直滚动条。它始终占着位,就不会因为突然出现或消失而引发抖动。

改完之后,导航栏彻底安静了。

5. 总结

路由守卫和导航栏改造,看起来是独立的功能,但本质上都在做同一件事:根据用户状态驱动视图变化。

  • 路由守卫:根据 isLogin 决定是否渲染页面。
  • 导航栏:根据 isLogin 决定显示哪些链接。

这是 React 的核心思维------一切都是组件,一切都可以通过条件渲染来实现。Vue 用全局钩子做的事,React 用组件来做。没有优劣之分,只有不同的设计哲学。

那个滚动条的 Bug,也让我意识到:前端开发中,很多诡异的问题,根因往往不是什么复杂的框架问题,而是一些基础 CSS 细节在捣鬼。耐心对比、逐个排查,就是最好的调试方法。

相关推荐
brien941 分钟前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance1 小时前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M1 小时前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写1 小时前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李1 小时前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易1 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
风骏时光牛马2 小时前
前沿视界
前端
kyriewen2 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员
陈随易2 小时前
3000 万人在用的翻译插件,重写一遍就是一门生意
前端·后端·程序员
IT_陈寒2 小时前
Vue的这个响应式陷阱,差点让我加班到天亮
前端·人工智能·后端