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 细节在捣鬼。耐心对比、逐个排查,就是最好的调试方法。

相关推荐
Daybreak1 小时前
从 PDD、DDD、SDD 到 TDD:我是如何用一套 Agent 工程方法论推进 My-Notion 的
前端
HjhIron2 小时前
从零实现一个待办事项应用:前端必学的Ajax与Node.js实战
前端·后端
yingyima2 小时前
JavaScript 正则表达式:从零开始的实战对比
前端
Sammyyyyy2 小时前
月之暗面 Kimi Code 0.4.0 发布,终端 AI 编码助手全面采用 TypeScript,实现毫秒级启动
前端·javascript·人工智能·ai·typescript·servbay
范什么特西2 小时前
配置文件xml和properties
xml·前端
jnene2 小时前
html 时间、价格筛选样式处理
前端·css·html
slongzhang_3 小时前
jquery 修复怪异模式html未声明“<!DOCTYPE html>”
前端·html·jquery
云水一下4 小时前
Vue.js从零到精通系列(三):组件化基础——Props、Emits、插槽与生命周期
前端·javascript·vue.js
SEO_juper4 小时前
新独立站冷启动收录全攻略:配置、推送、抓取配额优化完整手册
前端·谷歌·seo·跨境电商·外贸·geo·独立站