React学习:react-router-dom,从切图仔到前端工程师的必由之路

104 阅读8分钟

前言

还在用 a 标签跳转页面?还在为 SPA 应用的路由管理头秃?本文带你深入浅出 React Router v6,从基础的路由模式到高阶的懒加载、权限守卫、嵌套路由及动态高亮,手把手教你搭建一个企业级的路由架构。文末附带炫酷的 Loading 动画实现哦!

关键词:React, React Router v6, SPA, 路由鉴权, 懒加载


以前我们是这样“切图”的:

想当年(其实也没几年),前端由于缺乏路由概念,基本就是纯纯的“切图仔”。页面跳转?那是后端大佬的事。window.location.href 或者 < a href="xxx"> 一把梭,每次跳转页面都要白屏一下,去服务器重新拉取整个 HTML。

SnowShot_Video_2026-01-14_00-21-07.gif

后来,SPA(单页应用)  横空出世。前端终于站起来了!我们要掌控 URL,我们要实现“页面变了,但浏览器没刷新”的丝滑体验。

SnowShot_Video_2026-01-14_00-18-35.gif

这就轮到今天的主角登场了 —— React Router。它利用 HTML5 的 History API,让前端不仅能切图,还能管理“去哪儿”的问题。

一、路由模式:Hash 还是 Browser?

在 main.jsx 中,我们通常会看到这样的包裹:

import { BrowserRouter as Router } from "react-router-dom";

createRoot(document.getElementById("root")).render(
  <StrictMode>
    <Router>
      <App />
    </Router>
  </StrictMode>
);

这里有两个流派,选谁?

  1. BrowserRouter (推荐) :

    • 颜值:yoursite.com/about。干净又卫生。
    • 原理:基于 HTML5 history.pushState API。
    • 坑点:需要后端配合。不然刷新一下页面,Nginx 找不到这个路径,直接报 404。
  2. HashRouter:

    • 颜值:yoursite.com/#/about。那个 # 号就像脸上的青春痘,有点突兀。
    • 原理:利用 URL 的锚点(Hash)变化。
    • 优点:兼容性极好(连 IE 老古董都支持),不需要后端配置,纯前端自嗨。

结论

  • 做正经项目(尤其是有 SEO 需求的),请无脑选 BrowserRouter

  • 如果是内部后台、个人练习项目、演示 Demo、Electron 应用,想省事不折腾,直接无脑上 HashRouter

二、 路由配置:像搭积木一样简单

React Router v6 引入了 < Routes> 和 < Route>,比 v5 的 < Switch> 直观多了。

2.1 基础路由与 404

我们在src/router/index.jsx 里统一管理路由配置:

import {Roures,Route} from  'react-router-demo'
<Routes>
  {/* 首页 */}
  <Route path="/" element={<Home />} />
  {/* 关于页 */}
  <Route path="/about" element={<About />} />
  
  {/* 404 页面:路径写 * 代表匹配所有剩余路由 */}
  <Route path="*" element={<NotFound />} />
</Routes>

当用户迷路时,NotFound 组件不仅要提示,最好还能自动送他回家:

// pages/NotFound.jsx
import { useNavigate } from "react-router-dom";
import { useEffect } from "react";

export default function NotFound() {
  const navigate = useNavigate(); // 编程式导航 hook

  useEffect(() => {
    // 6秒后自动回首页,这是对路痴最后的温柔
    setTimeout(() => {
    // 跳转到path="/"
      navigate("/"); 
      // navigate("/", { replace: true }) // 如果不想留黑历史,加上 replace
    }, 6000);
  }, []);

  return <>404! 未找到没找到相关的路由,6秒后送你回家...</>;
}

2.2 动态路由:URL 里的秘密

电商网站经常有 /product/123 这种路径,123 是动态的。

image.png

{/* 定义参数名为 id */}
<Route path="/user/:id" element={<UserProfile />} />

组件里怎么拿?用 useParams:

// pages/UserProfile.jsx
import { useParams } from "react-router-dom";

export default function UserProfile() {
  const { id } = useParams(); // 解构出来的就是 URL 里的参数
  return <>正在查看 ID 为 {id} 的用户画像</>;
}

2.3 嵌套路由与 Outlet:画中画

很多后台管理系统,侧边栏和头部不变,只有中间区域在变。这时候就需要嵌套路由

SnowShot_Video_2026-01-14_00-44-40.gif

<Route path="/products" element={<Product />}>
  {/* 默认子路由 */}
  <Route path=":productId" element={<ProductDetail />} />
  <Route path="new" element={<NewProduct />} />
</Route>

注意!父组件 Product 必须留个“坑位”给子路由渲染,这个坑位就是 < Outlet>:

// pages/Product.jsx
import { Outlet } from "react-router-dom";

export default function Product() {
  return (
    <div className="product-layout">
      <h1>🛍️产品列表总览</h1>
      <hr />
      {/* 子路由渲染在这里 👇 */}
      <Outlet /> 
    </div>
  );
}

2.4 重定向:新老交替

项目重构了,老路径 /old-path 不想用了,但用户收藏夹里还是旧的怎么办?用 < Navigate>:

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

<Route path="/old-path" element={<Navigate replace to="/new-path" />} />
<Route path="/new-path" element={<NewPath />} />

replace 属性很重要,它意味着“替换”当前历史记录,用户点“后退”不会死循环。

三、 进阶技巧:从入门到精通

3.1 性能优化:路由懒加载 (Lazy Loading)

如果首屏就把 Pay、Admin 这种此时根本不用的组件代码加载下来,不仅浪费流量,页面加载还慢。
React 的 lazy 和 Suspense 是绝配。

改造前
import About from '../pages/About' (同步引入,打包进主包)

改造后

/src/router/index.jsx

import { lazy, Suspense } from "react";
import LoadingFallback from "../components/LoadingFallback"; // 自定义的 Loading 动画

// 只有当路由匹配时,才会去网络请求这个 JS 文件
const Home = lazy(() => import("../pages/Home"));
const About = lazy(() => import("../pages/About"));
const Product = lazy(() => import("../pages/product"));

export default function RouterConfig() {
  return (
    // Suspense 是必须的!因为加载代码需要时间,这期间显示 fallback
    <Suspense fallback={<LoadingFallback />}>
      <Routes>
        {/* ...路由列表 */}
      </Routes>
    </Suspense>
  );
}

3.2 路由鉴权:闲人免进

支付页面 /pay 是 VIP 禁地,没登录不能进。我们需要写一个高阶组件 (HOC)  或者包裹组件来实现路由守卫。

守卫组件 (ProtectRoute.jsx) :

/src/components/ProtectRoute.jsx
import { Navigate } from "react-router-dom";

export default function ProtectRoute({ children }) {
  // 真实项目中这里通常是读取 Redux/Zustand 中的 token
  const isLoggedIn = localStorage.getItem("isLogin") === "true";

  if (!isLoggedIn) {
    // 没登录?去登录页呆着吧,别忘了 replace
    return <Navigate to="/login" replace />;
  }

  // 登录了?请进
  return <>{children}</>;
}

路由配置:

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

3.3 手写导航高亮:精准控制

虽然 NavLink 组件自带高亮功能,但有时候我们需要更强的控制力。比如利用 useResolvedPath 和 useMatch 手写一个 active 类名判断逻辑。

看看 Navigation.jsx 里的骚操作:

import { Link, useResolvedPath, useMatch } from "react-router-dom";

// 自定义样式辅助函数
const isActive = (to) => {
  const resolvedPath = useResolvedPath(to); // 解析绝对路径
  // useMatch: 如果当前 URL 与 path 匹配,返回 match 对象,否则 null
  const match = useMatch({ 
    path: resolvedPath.pathname, 
    end: true // 精准匹配,不包含子路径
  });
  
  return match ? "active" : "";
};

// 使用
<Link to="/products" className={isActive("/products")}>Product</Link>

四、 视觉体验:拒绝枯燥的 Loading

为了让懒加载的等待时间不那么尴尬,我们手写了一个纯 CSS 的炫酷 Loading(位于 components/LoadingFallback)。

SnowShot_Video_2026-01-14_01-19-02.gif

/src/components/LoadingFallback/index.jsx

import styles from "./index.module.css";
export default function LoadingFallback() {
  return (
    <div className={styles.container}>
      <div className={styles.spinner}>
        <div className={styles.circle}></div>
        <div className={`${styles.circle} ${styles.circleInner}`}></div>
      </div>
      <p className={styles.text}>Loading...</p>
    </div>
  );
}

CSS样式

.container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100vh;
  background-color: rgba(255, 255, 255, 0.9);
}
.spinner {
  position: relative;
  width: 60px;
  height: 60px;
}
.circle {
  position: absolute;
  width: 100%;
  height: 100%;
  border: 4px solid transparent;
  border-top-color: #3498db;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}
.circleInner {
  width: 70%;
  height: 70%;
  top: 15%;
  left: 15%;
  border-top-color: #e74c3c;
  animation: spin 0.8s linear infinite reverse;
}
/* 关键帧动画 */
@keyframes spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

.text {
  margin-top: 20px;
  color: #2c3e50;
  font-size: 18px;
  font-weight: 500;
  animation: pulse 1.5s ease-in-out infinite;
}

@keyframes pulse {
  0% {
    opacity: 0.6;
  }
  50% {
    opacity: 1;
  }
  100% {
    opacity: 0.6;
  }
}

核心 CSS 知识点

  1. @keyframes spin: 控制旋转。
  2. animation: 组合动画。
  3. border-radius: 50% : 画圆。
  4. border-color: 利用透明边框制造缺口效果。
/* index.module.css 部分节选 */
.circle {
  border: 4px solid transparent; /* 透明底 */
  border-top-color: #3498db;     /* 只有顶部有色 */
  border-radius: 50%;            /* 变圆 */
  animation: spin 1s linear infinite; /* 转起来 */
}

五、进阶小技巧:Navigate

在 React Router v6 中, 是一个非常实用且高频使用的组件。你可以把它简单理解为  “路由界的自动传送门”

它的核心逻辑非常简单粗暴:只要这个组件被渲染(Render)出来,页面就会立刻跳转到指定位置。

以下是它的三个核心要点:

1. 核心作用:声明式导航

与 useNavigate(用于点击事件或异步操作后的跳转)不同, 专门用于渲染逻辑中。

场景一:重定向 (Redirect)
当用户访问一个旧的路径时,自动把他传送到新路径。
你的代码 router/index.jsx 中就有这个用法:

// 当访问 /old-path 时,立刻跳转到 /new-path
<Route path="/old-path" element={<Navigate replace to="/new-path" />} />

场景二:路由守卫 (Auth Guard)
当用户没有权限查看某个页面时,直接把他踢回登录页。
components/ProtectRoute.jsx 中正是这样用的:

if (!isLoggedIn) {
  // 没登录?渲染 Navigate 组件,立刻触发跳转
  return <Navigate to="/login" />;
}

2. 关键属性:replace

这是  最重要的属性,没有之一。

  • 不加 replace (默认) :相当于 history.push。浏览器会记录一条历史。

    • 后果:用户跳转后,点浏览器的“后退”按钮,会退回到跳转前的页面,然后因为逻辑又立刻触发跳转,导致用户陷入“后退死循环” ,出不来。
  • 加上 replace:相当于 history.replace。替换当前历史记录。

    • 后果:用户点“后退”按钮,会直接回到上上个页面,体验更佳

口诀做重定向或权限拦截时,务必加上 replace。

3. 如何传递参数?

你还可以通过 state 属性偷偷带点“私货”给目标页面:

<Navigate to="/login" state={{ from: "/pay", msg: "请先买票" }} replace />

在目标页面(Login)可以通过 useLocation().state 拿到这些数据,从而实现“登录后自动跳回刚才想去的页面”。


结论

  • useNavigate() :适合在 事件回调 中用(比如点按钮后跳转、请求成功后跳转)。
  • :适合在 组件渲染逻辑 中用(比如判定没权限直接跳、路径变更直接跳)。

六、 总结

React Router v6 相比 v5 做了大量减法,去掉了 withRouter,用 Hooks (useNavigate, useParams, useMatch) 取代了繁琐的高阶组件,代码量更少,逻辑更清晰。

记住这几点:

  1. BrowserRoutrer 是首选。
  2. Lazy + Suspense 是性能优化的标配。
  3. Outlet 是嵌套路由的灵魂。
  4. Navigate 是重定向和守卫的好帮手。

掌握了这些,你就不再是简单的“切图仔”,而是能掌控应用流向的“前端老司机”了!