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. 如果 isLoginfalse,跳转到欢迎页。
  3. 如果 isLogintrue,正常渲染页面。

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

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-allhover:scale-105font-bold 切换。
  2. 把所有可能引起布局变化的属性都移除了。
  3. 依然抖动。

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

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

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

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

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

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

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

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

5. 总结

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

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

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

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

相关推荐
陈随易1 小时前
编程语言级别的Skill市场,AI Agent 的未来形态
前端·后端·程序员
SoaringHeart2 小时前
Flutter进阶:基于 EasyRefresh 的下拉刷新封装 n_easy_refresh_mixin.dart
前端·flutter
IT_陈寒4 小时前
Vite的热更新突然不香了,排查三小时差点砸键盘
前端·人工智能·后端
子兮曰4 小时前
Agency-Agents 深度解析:400+ AI 专家的"梦之队"如何重塑开发工作流
前端·后端·vibecoding
竹林8185 小时前
用 The Graph 查询链上数据实战:从手搓 RPC 到 Subgraph,我的 NFT 项目数据加载快了 10 倍
前端·javascript
妙码生花5 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(十九):点选验证码代码逐行目检
前端·后端·go
Awu12276 小时前
⚡从零开发 Agent CLI(五)实现一个可治理、可扩展的工具系统
前端·人工智能·claude
咪库咪库咪6 小时前
Vue3-生命周期
前端
莪_幻尘7 小时前
你的 AI Skill 越多越蠢?Token 上下文爆炸的求生指南
前端·ai编程
lichenyang4537 小时前
从 has.echo 到异步 API 注册表:一次 ASCF API 回调不触发的排查复盘
前端