14、ReactJS - 路由(Router)

14、ReactJS - 路由(Router):从痛点到企业级实战,一篇讲透前端路由的前世今生

文章目录

  • [14、ReactJS - 路由(Router):从痛点到企业级实战,一篇讲透前端路由的前世今生](#14、ReactJS - 路由(Router):从痛点到企业级实战,一篇讲透前端路由的前世今生)
    • 一、痛点场景:没有路由的前端到底有多痛
    • [二、解决方案:React Router 如何破局](#二、解决方案:React Router 如何破局)
    • [三、React Router 是什么](#三、React Router 是什么)
      • [3.1 专业解释](#3.1 专业解释)
      • [3.2 大白话解释](#3.2 大白话解释)
      • [3.3 生活案例](#3.3 生活案例)
    • [四、为什么选择 React Router](#四、为什么选择 React Router)
    • [五、演进之路:从 v1 到 v7 的十年变迁](#五、演进之路:从 v1 到 v7 的十年变迁)
      • [5.1 v1 - v3 时代(2014 - 2017):集中式配置](#5.1 v1 - v3 时代(2014 - 2017):集中式配置)
      • [5.2 v4 - v5 时代(2017 - 2021):组件化路由](#5.2 v4 - v5 时代(2017 - 2021):组件化路由)
      • [5.3 v6 时代(2021 - 2024):现代路由的定型](#5.3 v6 时代(2021 - 2024):现代路由的定型)
      • [5.4 v6.4+ 时代(2022 至今):Data Router 的引入](#5.4 v6.4+ 时代(2022 至今):Data Router 的引入)
      • [5.5 v7 时代(2025+):全栈框架的融合](#5.5 v7 时代(2025+):全栈框架的融合)
    • 六、怎么用:从入门到企业级实战
      • [6.1 安装与基础配置](#6.1 安装与基础配置)
      • [6.2 核心组件与 Hooks](#6.2 核心组件与 Hooks)
      • [6.3 嵌套路由与布局](#6.3 嵌套路由与布局)
      • [6.4 动态路由与参数传递](#6.4 动态路由与参数传递)
      • [6.5 编程式导航](#6.5 编程式导航)
      • [6.6 企业级实战:权限路由守卫](#6.6 企业级实战:权限路由守卫)
      • [6.7 企业级实战:路由懒加载与代码分割](#6.7 企业级实战:路由懒加载与代码分割)
      • [6.8 企业级实战:Data Router 数据加载](#6.8 企业级实战:Data Router 数据加载)
    • 七、竞品对比:四大路由方案优劣势分析
    • 八、常用场景盘点
    • 九、面试官高频面试题
    • 十、总结

一、痛点场景:没有路由的前端到底有多痛

做前端开发的同学,大概率都经历过这样的场景:公司要做一个管理后台,里面有用户管理、订单管理、数据统计等十几个页面。最朴素的做法是什么?每个页面写一个独立的 HTML 文件,index.html、users.html、orders.html,页面之间用 <a href> 跳转。

听上去没问题,对吧?但真正做起来,痛点一个接一个:

痛点一:整页刷新,体验割裂。 从用户列表页点进用户详情页,浏览器整个白屏一下,再重新加载所有 CSS、JS、图片。一个后台系统动辄十几个页面,每次跳转都像在翻一本厚重的书,用户体验极其糟糕。

痛点二:状态丢失,数据重复请求。 你在列表页筛选了条件、翻到了第三页,点进详情再点返回,之前的筛选条件全没了,又得重新操作一遍。因为每次跳转都是全新的页面,内存中的状态荡然无存。

痛点三:公共组件重复渲染。 每个页面都有相同的顶部导航栏、侧边菜单,却要在每个 HTML 里重复写一遍。改一个导航链接,得去十几个文件里逐个修改,维护成本极高。

痛点四:前后端耦合严重。 每个页面对应后端一个路由,前端改个页面路径还得跟后端打招呼,部署也得跟着改 Nginx 配置。

这些痛点的根源是什么?就是传统多页应用(MPA)的页面跳转模型。浏览器天然就是"一个 URL 对应一个文档"的模式,而现代 Web 应用需要的是"一个应用内多个视图,切换时不刷新页面"。这,就是前端路由要解决的核心问题。

二、解决方案:React Router 如何破局

React Router 的核心思路一句话就能说清:把 URL 和组件之间建立映射关系,URL 变化时不刷新页面,而是动态渲染对应的组件。

具体来说,它做了这几件关键的事:

  1. 拦截浏览器导航行为:通过 History API 或 Hash 变化监听,在 URL 改变时阻止浏览器默认的整页刷新行为。
  2. 匹配路由规则:根据当前 URL 去匹配预先定义好的路由配置,找到对应的组件。
  3. 动态渲染组件 :把匹配到的组件渲染到页面上的指定位置(通常是 <Outlet /> 占位符),其他部分(导航栏、侧边栏)保持不动。
  4. 管理路由状态:提供路由参数、查询参数、导航状态等数据,让组件能感知当前路由上下文。

这样一来,页面切换变成了组件的卸载和挂载,没有网络请求、没有白屏、状态可以保留在内存中,用户体验瞬间提升了一个档次。这就是单页应用(SPA) 的核心魅力,而 React Router 就是 React 生态中实现 SPA 路由的事实标准。

三、React Router 是什么

3.1 专业解释

React Router 是一个基于 React 思想的声明式路由库,它通过组件化的方式将 URL 与 React 组件进行映射,实现客户端路由(Client-Side Routing)。它提供了 <BrowserRouter>、<Routes>、<Route>、<Link>、<Navigate>、<Outlet /> 等组件,以及 useParams、useNavigate、useLocation、useSearchParams 等 Hooks,让开发者能够以声明式、组件化的方式管理应用的导航逻辑。

从架构上看,React Router 分为三层:

  • react-router:核心库,提供路由匹配和导航的基础能力,与平台无关。
  • react-router-dom:在核心库基础上封装了 DOM 环境下的实现,是 Web 开发中实际使用的包。
  • react-router-native:面向 React Native 的路由实现。

3.2 大白话解释

说人话,React Router 就是一个**"URL 翻译官"**。你告诉它:"当地址是 /users 的时候,显示用户列表组件;当地址是 /users/123 的时候,显示 ID 为 123 的用户详情组件。"它就忠实地执行这个映射关系。用户在浏览器地址栏输入什么、点了什么链接,它就把对应的组件"搬"到页面上,而且整个过程不刷新页面,就像在手机 App 里切换页面一样流畅。

你可以把它想象成商场里的导览员。商场(你的应用)有很多楼层和店铺(组件),顾客(用户)说"我要去三楼的服装店"(输入 URL),导览员(React Router)不把整个商场拆掉重建,而是直接把顾客领到对应的店铺(渲染对应组件),商场的大门、电梯、走廊(导航栏、布局)始终都在。

3.3 生活案例

拿大家最熟悉的微信来类比。微信底部有四个 Tab:微信、通讯录、发现、我。点不同的 Tab,顶部标题栏和底部导航栏都不变,只有中间内容区域在切换。这就是典型的前端路由行为。

如果没有前端路由,微信每次切换 Tab 都要整个 App 重启一遍,聊天记录全没了,那还能用吗?React Router 干的就是这件事------让 Web 应用拥有原生 App 一样的页面切换体验。

四、为什么选择 React Router

了解了是什么之后,你可能会问:前端路由方案那么多,为什么偏偏是 React Router?它的优势体现在哪里?

第一,生态地位无可撼动。 React Router 是 React 官方推荐的路由方案,从 2014 年诞生至今已经迭代了十多年,GitHub 星标超过 5 万,npm 周下载量超过千万次。几乎所有 React 教程、开源项目、企业项目都在用它,遇到问题搜一下就有答案,这是其他方案无法比拟的生态优势。

第二,声明式 API 符合 React 哲学。 React 的核心思想是"UI 是状态的函数",React Router 把路由也变成了声明式的组件写法。你不需要命令式地监听 URL 变化、手动操作 DOM,只需要写 <Route path="/users" element={<Users />} /> 这样的配置,路由关系一目了然。

第三,嵌套路由能力强大。 企业级应用几乎都有复杂的布局嵌套:外层是整体布局,内层是页面内容,页面内可能还有 Tab 切换。React Router 的嵌套路由配合 <Outlet />,能优雅地处理任意层级的布局组合,这是它相比其他方案的核心竞争力之一。

第四,功能全面且持续进化。 从基础的路由匹配、参数传递,到权限守卫、懒加载、数据加载(loader/action)、表单处理,React Router 覆盖了企业级应用路由的所有场景。尤其是 v6.4 之后引入的 Data Router 和 v7 融合 Remix 后的全栈能力,让它不再只是一个"路由库",而是一个完整的应用框架。

第五,与 React 特性深度集成。 React Router 全面拥抱 Hooks,提供了 useNavigate、useParams、useLocation、useSearchParams、useLoaderData 等一系列 Hooks,与函数组件和 React 18 的并发特性无缝配合。

五、演进之路:从 v1 到 v7 的十年变迁

React Router 不是一天建成的,它的 API 经历了几次重大重构。了解这段历史,能帮你看懂为什么现在的 API 是这样设计的,也能在维护老项目时不至于一脸懵。

5.1 v1 - v3 时代(2014 - 2017):集中式配置

早期的 React Router 采用集中式路由配置,所有路由规则写在一个地方,通过 <Router>、<Route>、<IndexRoute> 嵌套定义。这就是你在很多老教程里看到的写法:

javascript 复制代码
import { Router, Route, IndexRoute, browserHistory } from 'react-router';

ReactDOM.render((
  <Router history={browserHistory}>
    <Route path="/" component={App}>
      <IndexRoute component={Home} />
      <Route path="about" component={About} />
      <Route path="contact" component={Contact} />
    </Route>
  </Router>
), document.getElementById('app'));

这个时期的特点是:路由配置集中、嵌套路由原生支持、但 API 偏命令式,与 React 的组件化思想融合得不够深入。

5.2 v4 - v5 时代(2017 - 2021):组件化路由

v4 是一次颠覆性的重构,核心理念变成了"一切皆组件 "。路由规则不再集中配置,而是分散在各个组件中,用 <Switch> 包裹多个 <Route>,匹配到第一个就渲染。同时包名也拆分成了 react-router-dom(Web 端)和 react-router-native(移动端)。

javascript 复制代码
import { BrowserRouter, Switch, Route, Link } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <nav>
        <Link to="/">首页</Link>
        <Link to="/about">关于</Link>
      </nav>
      <Switch>
        <Route exact path="/" component={Home} />
        <Route path="/about" component={About} />
      </Switch>
    </BrowserRouter>
  );
}

v5 在 v4 基础上做了优化,引入了 useParams、useLocation 等 Hooks,但核心 API 没变。这个时期的问题是:嵌套路由变得别扭(需要手动写 path 拼接)、<Switch> 的匹配顺序依赖书写顺序、exact 属性满天飞,容易出错。

5.3 v6 时代(2021 - 2024):现代路由的定型

v6 又是一次重大重构,吸收了 Reach Router 的优秀设计(排名匹配、嵌套路由回归),同时修复了 v5 的诸多痛点:

  • <Switch> 改为 <Routes>,路由匹配基于排名算法而非书写顺序,不再需要 exact。
  • component/render 属性统一为 element,直接传 JSX。
  • 嵌套路由回归,子路由的 path 支持相对路径。
  • 新增 <Outlet /> 作为子路由渲染占位符。
  • 全面 Hooks 化,useNavigate 替代 useHistory。
  • 移除了对 path-to-regexp 的依赖,包体积更小。
javascript 复制代码
import { BrowserRouter, Routes, Route, Outlet, Link } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Layout />}>
          <Route index element={<Home />} />
          <Route path="about" element={<About />} />
          <Route path="users/:id" element={<UserDetail />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}

5.4 v6.4+ 时代(2022 至今):Data Router 的引入

v6.4 引入了基于 Remix 理念的 Data Router,通过 createBrowserRouter 创建路由,支持 loader(数据加载)、action(表单处理)、errorElement(错误边界)等能力,把数据获取和路由绑定在一起,避免了传统的 useEffect 拉数据模式。

javascript 复制代码
import { createBrowserRouter, RouterProvider } from 'react-router-dom';

const router = createBrowserRouter([
  {
    path: '/users/:id',
    element: <UserDetail />,
    loader: async ({ params }) => {
      const res = await fetch(`/api/users/${params.id}`);
      return res.json();
    },
    errorElement: <ErrorPage />,
  },
]);

function App() {
  return <RouterProvider router={router} />;
}

5.5 v7 时代(2025+):全栈框架的融合

v7 是 React Router 的又一个里程碑,它正式融合了 Remix 框架的全栈能力,从一个纯客户端路由库演进为支持服务端渲染(SSR)、服务端函数、渐进增强的全栈框架。v7 保持了对 v6 API 的高度兼容,同时提供了 react-router 统一包名,支持 SSR、流式渲染、服务端 loaders/actions 等能力。

这十年的演进,本质上是在回答一个问题:路由到底应该只是 URL 到组件的映射,还是应该承担更多应用架构的职责? React Router 的答案是后者,它正在从一个路由库成长为一个完整的应用框架。

六、怎么用:从入门到企业级实战

理论讲了这么多,接下来进入实战环节。我会从最基础的安装配置讲起,逐步深入到企业项目中的权限控制、懒加载、数据加载等高级用法。所有代码都基于当前主流的 React Router v6。

6.1 安装与基础配置

首先安装依赖。Web 项目安装 react-router-dom,它会自动引入核心库 react-router:

bash 复制代码
npm install react-router-dom

或者使用 yarn / pnpm:

bash 复制代码
yarn add react-router-dom
pnpm add react-router-dom

安装完成后,在应用入口用 <BrowserRouter> 包裹整个应用,然后用 <Routes> 和 <Route> 定义路由规则:

jsx 复制代码
// src/main.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Layout from './layouts/Layout';
import Home from './pages/Home';
import About from './pages/About';
import NotFound from './pages/NotFound';

const root = ReactDOM.createRoot(document.getElementById('root'));

root.render(
  <BrowserRouter>
    <Routes>
      <Route path="/" element={<Layout />}>
        <Route index element={<Home />} />
        <Route path="about" element={<About />} />
        <Route path="*" element={<NotFound />} />
      </Route>
    </Routes>
  </BrowserRouter>
);

几个关键点:

  • <BrowserRouter> 使用 HTML5 History API,URL 干净(如 /about),但需要服务端配置支持,否则刷新会 404。
  • <Route path="*"> 是通配路由,匹配所有未定义的路径,通常用于 404 页面。
  • <Route index> 是索引路由,当父路径匹配但没有子路径时渲染,比如访问 / 时渲染 <Home />。

6.2 核心组件与 Hooks

React Router v6 提供的核心 API 可以分为组件和 Hooks 两类:

常用组件:

组件 作用
<BrowserRouter> 基于 History API 的路由容器,URL 干净
<HashRouter> 基于 URL hash 的路由容器,兼容性好,URL 带 #
<Routes> 路由匹配容器,替代 v5 的 <Switch>
<Route> 单条路由规则,定义 path 与 element 的映射
<Link> 声明式导航链接,替代 <a>,不刷新页面
<NavLink> 带激活状态的导航链接,适合菜单高亮
<Navigate> 声明式重定向组件
<Outlet /> 子路由渲染占位符,嵌套路由的核心

常用 Hooks:

Hook 作用
useNavigate() 获取导航函数,实现编程式跳转
useParams() 获取动态路由参数,如 /users/:id 中的 id
useLocation() 获取当前 location 对象,包含 pathname、search、state 等
useSearchParams() 获取和设置 URL 查询参数
useRouteError() 获取路由加载/渲染时的错误信息
useLoaderData() 获取 Data Router 中 loader 返回的数据

6.3 嵌套路由与布局

嵌套路由是企业项目中最常用的特性之一。典型的后台系统布局是:外层有侧边栏和顶部导航,内层是具体页面内容。用嵌套路由实现非常优雅:

jsx 复制代码
// src/layouts/Layout.jsx
import { Outlet, NavLink } from 'react-router-dom';

export default function Layout() {
  return (
    <div className="app-layout">
      <aside className="sidebar">
        <NavLink to="/" end className={({ isActive }) => isActive ? 'active' : ''}>
          首页
        </NavLink>
        <NavLink to="/users" className={({ isActive }) => isActive ? 'active' : ''}>
          用户管理
        </NavLink>
        <NavLink to="/orders" className={({ isActive }) => isActive ? 'active' : ''}>
          订单管理
        </NavLink>
      </aside>
      <main className="content">
        {/* 子路由渲染到这里 */}
        <Outlet />
      </main>
    </div>
  );
}

路由配置中,子路由写在父路由的内部:

jsx 复制代码
<Routes>
  <Route path="/" element={<Layout />}>
    <Route index element={<Home />} />
    <Route path="users" element={<Users />} />
    <Route path="orders" element={<Orders />} />
  </Route>
</Routes>

这样,无论切换到哪个子页面,侧边栏都保持不变,只有 <Outlet /> 的内容在切换。这就是嵌套路由的威力------布局与内容分离,公共部分只渲染一次。

6.4 动态路由与参数传递

企业项目中大量使用动态路由,比如用户详情页 /users/123、商品详情页 /products/456。用 :paramName 语法定义动态段:

jsx 复制代码
<Route path="/users/:userId" element={<UserDetail />} />

在组件中通过 useParams() 获取参数:

jsx 复制代码
// src/pages/UserDetail.jsx
import { useParams, Link } from 'react-router-dom';
import { useEffect, useState } from 'react';

export default function UserDetail() {
  const { userId } = useParams();
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => setUser(data));
  }, [userId]);

  if (!user) return <div>加载中...</div>;

  return (
    <div>
      <h1>{user.name}</h1>
      <p>邮箱:{user.email}</p>
      <Link to="/users">返回列表</Link>
    </div>
  );
}

查询参数(URL 中 ?key=value 的部分)通过 useSearchParams() 操作:

jsx 复制代码
import { useSearchParams } from 'react-router-dom';

export default function UserList() {
  const [searchParams, setSearchParams] = useSearchParams();
  const page = searchParams.get('page') || 1;
  const keyword = searchParams.get('keyword') || '';

  const handleSearch = (e) => {
    e.preventDefault();
    setSearchParams({ keyword: e.target.keyword.value, page: 1 });
  };

  return (
    <div>
      <form onSubmit={handleSearch}>
        <input name="keyword" defaultValue={keyword} placeholder="搜索用户" />
        <button type="submit">搜索</button>
      </form>
      <p>当前第 {page} 页,关键词:{keyword}</p>
    </div>
  );
}

6.5 编程式导航

除了用 <Link> 做声明式导航,很多场景需要在代码逻辑中跳转,比如登录成功后跳转到首页、表单提交后跳转到列表页。用 useNavigate():

jsx 复制代码
import { useNavigate } from 'react-router-dom';

export default function LoginPage() {
  const navigate = useNavigate();

  const handleLogin = async (e) => {
    e.preventDefault();
    const formData = new FormData(e.target);
    const res = await fetch('/api/login', {
      method: 'POST',
      body: JSON.stringify(Object.fromEntries(formData)),
    });

    if (res.ok) {
      // 跳转到首页,replace 为 true 时不保留历史记录
      navigate('/', { replace: true });
    }
  };

  return (
    <form onSubmit={handleLogin}>
      <input name="username" placeholder="用户名" />
      <input name="password" type="password" placeholder="密码" />
      <button type="submit">登录</button>
    </form>
  );
}

navigate 还支持前进后退:

javascript 复制代码
navigate(-1);  // 后退一步
navigate(1);   // 前进一步

6.6 企业级实战:权限路由守卫

企业项目几乎都需要权限控制:未登录用户不能访问后台页面,普通用户不能访问管理员页面。React Router v6 没有内置的路由守卫 API,但通过高阶组件(HOC)的方式可以优雅实现。

第一步:创建权限守卫组件

jsx 复制代码
// src/components/ProtectedRoute.jsx
import { Navigate, useLocation } from 'react-router-dom';
import { useAuth } from '../hooks/useAuth';

export default function ProtectedRoute({ children, requiredRole }) {
  const { isAuthenticated, user } = useAuth();
  const location = useLocation();

  // 未登录,重定向到登录页,并记录来源地址
  if (!isAuthenticated) {
    return <Navigate to="/login" state={{ from: location }} replace />;
  }

  // 已登录但角色不满足,显示无权限页面
  if (requiredRole && user.role !== requiredRole) {
    return <Navigate to="/403" replace />;
  }

  return children;
}

第二步:在路由配置中使用

jsx 复制代码
<Routes>
  {/* 公开页面 */}
  <Route path="/login" element={<Login />} />
  <Route path="/403" element={<Forbidden />} />

  {/* 需要登录的页面 */}
  <Route path="/" element={
    <ProtectedRoute>
      <Layout />
    </ProtectedRoute>
  }>
    <Route index element={<Home />} />
    <Route path="users" element={<Users />} />

    {/* 需要管理员角色 */}
    <Route path="admin" element={
      <ProtectedRoute requiredRole="admin">
        <AdminDashboard />
      </ProtectedRoute>
    } />
  </Route>

  <Route path="*" element={<NotFound />} />
</Routes>

第三步:登录页跳回来源地址

jsx 复制代码
// src/pages/Login.jsx
import { useNavigate, useLocation } from 'react-router-dom';

export default function Login() {
  const navigate = useNavigate();
  const location = useLocation();
  const from = location.state?.from?.pathname || '/';

  const handleLogin = async () => {
    // ... 登录逻辑
    navigate(from, { replace: true });
  };

  return <button onClick={handleLogin}>登录</button>;
}

这样就实现了完整的权限控制链路:未登录跳转登录页、登录后跳回原页面、角色不足显示 403。

6.7 企业级实战:路由懒加载与代码分割

大型应用如果把所有页面代码打包到一个 JS 文件里,首屏加载会非常慢。React Router 配合 React 的 React.lazy 和 Suspense 可以轻松实现路由级别的代码分割,每个页面按需加载:

jsx 复制代码
// src/router/index.jsx
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Layout from '../layouts/Layout';
import Loading from '../components/Loading';

// 懒加载页面组件
const Home = lazy(() => import('../pages/Home'));
const Users = lazy(() => import('../pages/Users'));
const UserDetail = lazy(() => import('../pages/UserDetail'));
const Orders = lazy(() => import('../pages/Orders'));
const AdminDashboard = lazy(() => import('../pages/AdminDashboard'));

export default function AppRouter() {
  return (
    <BrowserRouter>
      <Suspense fallback={<Loading />}>
        <Routes>
          <Route path="/" element={<Layout />}>
            <Route index element={<Home />} />
            <Route path="users" element={<Users />} />
            <Route path="users/:id" element={<UserDetail />} />
            <Route path="orders" element={<Orders />} />
            <Route path="admin" element={<AdminDashboard />} />
          </Route>
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

这样,用户访问 /users 时才会加载 Users 页面的 JS 代码,首屏只加载 Layout 和 Home 的代码,大大提升首屏速度。配合 Vite 或 Webpack 的 chunk 分割,还能进一步优化。

6.8 企业级实战:Data Router 数据加载

传统模式下,组件渲染后通过 useEffect 拉数据,会经历"渲染空状态 -> 请求数据 -> 重新渲染"的过程,体验不够好。v6.4 引入的 Data Router 把数据加载前置到路由层,组件渲染时数据已经准备好了。

jsx 复制代码
// src/router/index.jsx
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import Layout from '../layouts/Layout';
import Home from '../pages/Home';
import UserList from '../pages/UserList';
import UserDetail from '../pages/UserDetail';
import ErrorPage from '../pages/ErrorPage';

const router = createBrowserRouter([
  {
    path: '/',
    element: <Layout />,
    errorElement: <ErrorPage />,
    children: [
      { index: true, element: <Home /> },
      {
        path: 'users',
        element: <UserList />,
        // 进入路由前自动加载数据
        loader: async () => {
          const res = await fetch('/api/users');
          if (!res.ok) throw new Response('加载失败', { status: 500 });
          return res.json();
        },
      },
      {
        path: 'users/:id',
        element: <UserDetail />,
        loader: async ({ params }) => {
          const res = await fetch(`/api/users/${params.id}`);
          if (!res.ok) throw new Response('用户不存在', { status: 404 });
          return res.json();
        },
      },
    ],
  },
]);

export default function App() {
  return <RouterProvider router={router} />;
}

在组件中通过 useLoaderData() 直接获取数据:

jsx 复制代码
// src/pages/UserList.jsx
import { useLoaderData, Link } from 'react-router-dom';

export default function UserList() {
  const users = useLoaderData();

  return (
    <div>
      <h1>用户列表</h1>
      <table>
        <thead>
          <tr>
            <th>ID</th>
            <th>姓名</th>
            <th>邮箱</th>
            <th>操作</th>
          </tr>
        </thead>
        <tbody>
          {users.map(user => (
            <tr key={user.id}>
              <td>{user.id}</td>
              <td>{user.name}</td>
              <td>{user.email}</td>
              <td>
                <Link to={`/users/${user.id}`}>查看详情</Link>
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

Data Router 还支持 action 处理表单提交、shouldRevalidate 控制数据重新加载时机,这些能力在企业项目中能大幅简化数据管理逻辑。

七、竞品对比:四大路由方案优劣势分析

前端路由领域不止 React Router 一家,了解各方案的优劣势,才能在项目选型时做出最优决策。

对比维度 React Router v6/v7 Next.js App Router TanStack Router Reach Router
定位 通用客户端路由库 全栈框架内置路由 类型优先的现代路由库 简洁可访问的路由库
路由定义方式 组件式 <Route> 或对象式配置 文件系统约定(app 目录结构) 对象式配置 + 类型推断 组件式 <Router>
嵌套路由 原生支持,<Outlet /> 占位 原生支持,layout.tsx 约定 原生支持 原生支持
类型安全 运行时匹配,参数需手动声明类型 基于文件系统自动推断 全链路类型安全,搜索参数也有类型 较弱
SSR/SSG v7 支持 SSR,v6 需自行配置 开箱即用,SSR/SSG/ISR/PPR 支持(TanStack Start) 不支持
数据加载 v6.4+ loader/action,v7 服务端 loader Server Components、fetch 缓存 loader + 类型化数据 不支持
包体积 约 10KB(gzip) 框架级,体积较大 约 8KB(gzip) 约 6KB(gzip)
生态成熟度 极高,十年积累,社区庞大 极高,Vercel 背书,增长迅猛 中等,TanStack 生态加持 已停止维护,并入 React Router
学习曲线 平缓,API 直观 较陡,约定大于配置,概念多 中等,类型驱动需要适应 平缓
适用场景 通用 SPA、企业后台、需要灵活控制架构 内容站、电商、SEO 敏感型应用、全栈项目 注重类型安全的中大型 TS 项目 老项目维护,不建议新项目使用
主要优势 灵活通用、生态成熟、嵌套路由强大、v7 全栈化 SSR/SSG 开箱即用、Vercel 部署零配置、性能优化内置 端到端类型安全、搜索参数类型推断、现代 API 设计 API 简洁、可访问性好、排名匹配
主要劣势 v6 之前版本碎片化严重、SSR 需额外配置(v7 前) 与 Vercel 深度绑定、自建部署复杂、框架约束多 生态较新、社区资源相对少、学习成本 已停止维护、功能有限、不支持数据加载

选型建议:

  • 如果你做的是企业内部管理后台、通用 SPA,React Router 是最稳妥的选择,生态成熟、招人容易、灵活度高。
  • 如果你的项目强依赖 SEO (官网、博客、电商),或者需要服务端渲染,Next.js 是更优解,它的路由是框架内置能力,不需要单独引入 React Router。
  • 如果你的团队极度重视 TypeScript 类型安全,且愿意尝试新事物,TanStack Router 提供了 React Router 目前还不具备的全链路类型推断能力。
  • Reach Router 已经正式并入 React Router,新项目不要再使用。

八、常用场景盘点

结合企业项目经验,React Router 在以下场景中发挥着关键作用:

场景一:企业管理后台。 这是 React Router 最经典的应用场景。嵌套路由处理侧边栏布局、动态路由处理详情页、权限守卫控制访问范围、懒加载优化首屏速度,几乎所有特性都能用上。

场景二:电商商品详情与购物车。 商品列表到详情的动态路由、搜索筛选条件通过查询参数持久化(刷新页面条件不丢失)、购物车状态通过路由 state 传递,都是常见用法。

场景三:内容平台与博客。 文章列表、文章详情、分类归档、标签页,通过嵌套路由和动态路由组织内容结构,配合 Data Router 的 loader 预加载文章数据。

场景四:多步骤表单与向导。 注册流程、订单结账等多步骤场景,用路由管理每一步的状态,用户可以通过浏览器前进后退在步骤间切换,刷新也能回到当前步骤。

场景五:登录认证与权限体系。 登录页与主应用分离、登录后跳回来源页、基于角色的路由级权限控制、Token 过期自动跳转登录,这些都是权限守卫的典型应用。

场景六:Tab 切换与面板管理。 页面内的 Tab 切换也可以用路由管理,把 Tab 状态同步到 URL 查询参数,实现可分享、可刷新的 Tab 状态。

九、面试官高频面试题

路由是前端面试的必考点,以下是企业面试中出现频率最高的题目及参考答案,建议收藏。

面试题一:BrowserRouter 和 HashRouter 有什么区别?

参考答案:

两者都是 React Router 的顶层路由容器,核心区别在于 URL 形式和底层实现:

  • BrowserRouter(history 模式) :使用 HTML5 History API(pushState、replaceState、popstate 事件),URL 干净美观(如 /users/123),但需要服务端配置支持------因为刷新时浏览器会向服务器请求完整路径,如果服务器没有对应资源就会返回 404。解决方案是在 Nginx 等服务器上配置所有路由都 fallback 到 index.html,由前端路由接管。
  • HashRouter(hash 模式) :使用 URL 的 hash 部分(# 后面的内容),如 /#/users/123。hash 变化不会触发服务器请求,所以刷新不会 404,不需要服务端配置。但 URL 中带 #,不够美观,且对 SEO 不友好。

选型原则:正式项目优先用 BrowserRouter,配合服务端配置;静态页面、演示项目、无法配置服务端时用 HashRouter。

面试题二:React Router v5 和 v6 有哪些主要区别?

参考答案:

  1. <Switch> 改为 <Routes>,匹配算法从"按顺序匹配第一个"改为"排名匹配算法",自动选择最优匹配,不再需要 exact。
  2. component 和 render 属性统一为 element,直接传入 JSX 元素。
  3. 嵌套路由回归,子路由 path 支持相对路径,通过 <Outlet /> 渲染子路由。
  4. useHistory 改为 useNavigate,API 更简洁。
  5. 移除了 withRouter 高阶组件,统一使用 Hooks。
  6. 移除了对 path-to-regexp 的依赖,路径语法更简单,包体积更小。
  7. 新增 useSearchParams 方便操作查询参数。
  8. v6.4 引入 Data Router(createBrowserRouter),支持 loader/action/errorElement。

面试题三:如何实现路由守卫(权限控制)?

参考答案:

React Router v6 没有内置守卫 API,通过高阶组件模式实现。创建一个 ProtectedRoute 组件,内部判断登录状态和角色权限,不满足条件时用 <Navigate> 重定向到登录页或 403 页。在路由配置中用 ProtectedRoute 包裹需要保护的组件即可。具体实现见本文 6.6 节。

面试题四:路由参数有哪些传递方式?如何获取?

参考答案:

  1. 动态路由参数 :在 path 中用 :paramName 定义,如 /users/:id,通过 useParams() 获取。
  2. 查询参数 :URL 中 ?key=value 部分,通过 useSearchParams() 获取和设置。
  3. state 参数 :通过 <Link state={...}> 或 navigate(to, { state: {...} }) 传递,通过 useLocation().state 获取,数据不显示在 URL 中,适合传递敏感或大量数据。
  4. 路径通配符 :path="*" 匹配任意路径,通过 useParams().['*'] 获取匹配到的剩余路径。

面试题五:history 模式下刷新页面为什么会 404?怎么解决?

参考答案:

原因 :history 模式使用 pushState 修改 URL,但这个操作只改变浏览器地址栏,不会向服务器发送请求。当用户刷新页面时,浏览器会向服务器发起对当前完整 URL 的 HTTP 请求,而服务器上并没有对应的物理文件或路由(因为这是前端路由),所以返回 404。

解决方案 :在服务端配置 URL 重写规则,将所有非静态资源的请求都重定向到 index.html,让前端路由接管页面渲染。以 Nginx 为例:

nginx 复制代码
location / {
  try_files $uri $uri/ /index.html;
}

Apache、Node.js 服务器(Express、Koa)也有对应的配置方式。本地开发时 Vite、Webpack DevServer 已经内置了这个配置,所以本地刷新不会 404,只有部署到生产环境才需要手动配置。

面试题六:React Router 的嵌套路由是如何工作的?

参考答案:

嵌套路由的核心是 <Outlet /> 组件。父路由的 element 中放置 <Outlet /> 作为子路由的渲染占位符。当 URL 匹配到子路由时,React Router 会将子路由的 element 渲染到父组件的 <Outlet /> 位置,父组件的其他部分(导航栏、侧边栏)保持不变。子路由的 path 可以写相对路径(不需要写完整的父路径前缀),React Router 会自动拼接。这种设计实现了布局与内容的分离,是企业级应用中处理复杂布局的标准方案。

面试题七:如何实现路由懒加载?

参考答案:

配合 React 的 React.lazy 和 Suspense 实现。用 lazy(() => import('./pages/xxx')) 动态导入页面组件,用 <Suspense fallback={<Loading />}> 包裹 <Routes>,在组件加载过程中显示 loading 状态。这样每个路由对应的代码会被分割成独立的 chunk,访问时才加载,优化首屏性能。具体实现见本文 6.7 节。

面试题八:<Link> 和 <a> 标签有什么区别?

参考答案:

<a> 标签是浏览器原生的超链接,点击会触发整页刷新,向服务器请求新页面。<Link> 是 React Router 提供的导航组件,底层也是渲染成 <a> 标签,但会拦截点击事件,通过 History API 修改 URL 并触发前端路由匹配,不刷新页面。因此在 SPA 内部导航必须用 <Link>,只有跳转到外部链接时才用原生 <a>。

十、总结

React Router 是 React 生态中不可或缺的基础设施。从最初的集中式配置,到组件化路由,再到如今的 Data Router 和全栈框架化,它的每一次演进都在回应前端应用复杂度提升带来的新需求。

对于开发者来说,掌握 React Router 不仅仅是学会几个 API,更是理解前端路由的本质------URL 与视图的映射、状态与导航的协同、布局与内容的分离。这些思想在任何前端框架中都是通用的。

希望这篇文章能帮你从"会用"进阶到"理解",在企业项目中写出更优雅、更健壮的路由代码。如果觉得有帮助,欢迎点赞收藏,也欢迎在评论区交流你的路由实践经验。


本文为原创文章,如需转载,请联系作者获得授权,并注明出处。

相关推荐
原则猫1 小时前
UDP 到底怎么实现可靠的
前端
裕晟资质规划2 小时前
三类涉密资质延续时限差异分析:军工60日与集成印制3个月的边界
java·服务器·前端·网络·安全
ShineWinsu3 小时前
对于JavaScript:JavaScript基础语法的解析
开发语言·前端·javascript·面试·web·基础语法·循环
月夏3 小时前
Vue3 转 React:18 项对照表 + 5 个必踩的坑(附 7 天上手路线)
前端
广州华水科技3 小时前
单北斗GNSS位移监测系统的应用与维护解析
前端
星恒随风3 小时前
Linux线程概念详解:从分页存储、页表、MMU与TLB,深入理解线程和进程的区别与联系
linux·运维·笔记·学习·状态模式
梦曦i3 小时前
uni-storage 0.1.0 发布:TTL过期管理+多实例隔离
前端·storage·uni-app x
猫头_4 小时前
AI 流式传输工程指南:模型吐出半截 JSON,前端如何边收边渲染?
前端·javascript·llm
console.log('npc')5 小时前
第 1 章 项目概述与环境搭建
前端·人工智能·大模型