React Router 6 路由
【面试速答版】
Q1: “React Router 6 相比 React Router 5 有哪些主要变化?”
核心变化是 API 全面升级:<Switch> 改为 <Routes>(自动匹配最佳路由);component={X} 改为 element={<X />};useHistory() 改为 useNavigate();路由参数从 props.match.params 改为 useParams() hook。V6.4+ 引入了基于数据路由的 API——createBrowserRouter + RouterProvider,支持 loader(路由组件渲染前自动执行数据预加载)和 action(表单提交处理),类似 Next.js 的约定式数据加载。此外,嵌套路由通过 <Outlet> 组件渲染子路由,不再需要手动嵌套 Route 组件。
Q2: “React Router 6 中的嵌套路由和布局路由是怎么实现的?”
通过 <Outlet /> 组件。在父路由的 element 中放入 <Outlet />,子路由的组件就会渲染在这个位置。结合布局组件(如 Header + Footer + Outlet),可以实现”页眉/页脚不变,中间内容区域切换”的效果。在路由配置中,父子关系通过 children 数组体现:
1 | const router = createBrowserRouter([ |
Q3: “React Router 6 的 loader 和 action 是什么?”
loader 是路由级别的数据预加载函数——进入路由前自动执行,返回的数据通过 useLoaderData() 在组件中获取。action 是路由级别的表单提交处理函数——当 <Form> 组件提交时自动调用,返回的数据通过 useActionData() 获取。两者都属于 V6.4+ 的”数据路由 API”(需要 createBrowserRouter + RouterProvider),让”获取数据 → 等待 → 渲染页面”这个流程更声明式,不需要在组件中用 useEffect 手动控制。
【深入理解版】
1. 这个知识点要解决什么问题?
在单页应用(SPA)中,路由解决三个核心问题:
- URL 与 UI 的对应:访问
/user/123显示用户详情页,访问/about显示关于页。刷新页面后不会回到首页,而是停留在当前路由。 - 前进/后退:浏览器的前进/后退按钮正常工作。
- 导航过程中的控制:登录检查、权限验证、数据预加载、离开确认。
React Router 是 React 生态中最主流的路由方案。V6 是 2022 年发布的重大升级,核心改进是让路由和数据加载更紧密地配合——V6.4+ 的 loader 允许你在渲染组件之前就把数据准备好,不再需要在组件中写 useEffect 发请求。
2. 核心原理/执行过程
2.1 两种路由创建方式
React Router 6 提供了两种方式创建路由,适用不同场景:
方式一:组件式(适合中小项目)
1 | import { BrowserRouter, Routes, Route, Link } from 'react-router-dom' |
<BrowserRouter> 使用 HTML5 History API(pushState、popstate)来同步 URL 和 UI,不依赖 URL 中的 #。URL 看起来像普通网站:https://example.com/user/123。
方式二:数据路由 API(适合中大型项目)
1 | import { createBrowserRouter, RouterProvider } from 'react-router-dom' |
createBrowserRouter 接收一个路由配置数组(而不是 JSX 组件),返回一个 router 实例,然后通过 <RouterProvider> 注入到应用中。这种方式的优势是:支持 loader 和 action,且 router 实例可以在组件外部访问(用于编程式导航)。
2.2 嵌套路由与布局路由
嵌套路由是 V6 的核心设计——父路由的 element 中放置 <Outlet />,子路由渲染在该位置:
1 | // 路由配置 |
<Outlet /> 可以理解为”子路由内容插槽”。父组件做布局(Header + Outlet + Footer),子组件只负责自己的内容。
索引路由(index route):{ index: true, element: <Home /> } 表示当路径就是 / 时渲染 Home,不需要另一个 path: ''。
2.3 导航与参数
1 | import { useNavigate, useParams, useSearchParams } from 'react-router-dom' |
这里出现的三个 hook:
- **
useParams()**:返回 URL 中的动态参数(路由路径中:id匹配的部分) - **
useNavigate()**:返回编程式导航函数,替代 V5 的useHistory().push() - **
useSearchParams()**:类似useState,但数据源是 URL query 参数。searchParams.get('key')读取,setSearchParams({ key: 'val' })写入
2.4 loader 与 action — 数据路由的核心
这是 V6.4+ 最值得关注的新能力。loader 在路由组件渲染之前自动执行,用于预加载数据:
1 | const router = createBrowserRouter([ |
1 | function Product() { |
loader 解决了什么问题? 在 V6.4 之前,获取数据要在组件中用 useEffect + fetch。这导致两个问题:① 组件先渲染空内容,数据回来后再填充(闪白);② 每个页面都要重复写 useEffect 里的 loading/error 逻辑。loader 让数据获取在渲染之前完成,路由切换时会等待 loader 返回再渲染页面。如果 loader 抛异常,渲染 errorElement 而不是白屏。
action 处理表单提交,与 loader 对称:
1 | const router = createBrowserRouter([ |
3. 实际应用场景
场景1:权限控制(路由守卫)
React Router 6 没有像 Vue Router 那样的 beforeEach 钩子,但可以通过 loader 做权限验证:
1 | function authLoader() { |
redirect() 函数中断当前导航,跳转到指定路径。注意它要在 loader 中 return,而不是 throw。
场景2:导航离开确认
1 | import { useBlocker } from 'react-router-dom' |
4. 常见误区 & 实际项目中的坑
误区1:在组件外部使用 useNavigate
1 | // ❌ useNavigate 必须在 React 组件中调用 |
解法:使用 createBrowserRouter 返回的 router 实例:
1 | const router = createBrowserRouter([...]) |
误区2:忘记给 History 模式配置服务器 fallback
1 | # 如果使用 BrowserRouter(HTML5 History 模式) |
如果是 Vite 开发环境,vite.config.js 中需要加 historyApiFallback: true。
误区3:loader 中直接修改组件状态
1 | // ❌ loader 中不能访问 React 状态 |
loader 的定位:它只是一个普通的异步函数,返回数据供组件消费,不在 loader 中调用 React 的 API。
5. 与相关知识的关联 & 对比
| 对比维度 | React Router 5 | React Router 6 | Vue Router 4 |
|---|---|---|---|
| 核心组件 | Switch → Route | Routes → Route / RouterProvider | router-view / router-link |
| 编程式导航 | useHistory() | useNavigate() | router.push() |
| 路由参数 | props.match.params | useParams() | useRoute().params |
| 嵌套路由 | Route 嵌套 Route | ||
| 数据加载 | 组件内 useEffect | loader(路由级别) | 无内置 |
| 表单处理 | 手动提交 | action + Form 组件 | 无内置 |
| 路由守卫 | 手动实现 | loader 中 redirect | router.beforeEach |
| 支持数据路由 | 否 | 是(V6.4+) | 否 |
6. 现代最佳实践(2024-2025)
- **新项目使用
createBrowserRouter+RouterProvider**,而不是<BrowserRouter>。数据路由 API 提供了 loader/action,未来会是 React Router 的主要发展方向。 - 利用 loader 做数据预加载和权限校验,不再在组件中用
useEffect发请求和检查登录。 - 嵌套路由 + Outlet 实现布局复用:一个布局组件 + 多个子路由,避免在每个路由组件中重复写 Header/Footer。
- 配合 React.lazy 做路由级代码分割:
1 | const Dashboard = React.lazy(() => import('./Dashboard')) |
- 路由配置集中管理:把路由配置放在独立的
router.jsx文件中,不要散落在 App 组件中。
7. 常见疑问解答
Q:createBrowserRouter 和 <BrowserRouter> 有什么区别?用哪个?
A:createBrowserRouter 是 V6.4+ 新增的”数据路由 API”,基于标准 History API,但返回的是一个 router 实例(普通 JS 对象),通过 RouterProvider 注入 React 树。<BrowserRouter> 是传统组件式用法,直接在 React 树中用组件配置路由。区别在于:createBrowserRouter 支持 loader/action,且 router 实例可以在组件外访问。**新项目建议用 createBrowserRouter**,因为它代表了 React Router 未来的方向。
Q:loader 和直接在 useEffect 中 fetch 有什么不同?
A:核心区别是执行时机。loader 在组件渲染之前执行——React Router 会等待 loader 完成后再渲染路由组件。useEffect + fetch 在组件渲染之后执行——组件先渲染”空”状态,数据回来后再渲染”有数据”状态,用户会看到从空到有数据的闪烁。loader 避免了这种闪烁,同时也把数据获取逻辑从组件中移出,让组件更纯粹。
Q:<Link> 和 <a> 标签在 React Router 中有什么区别?
A:<Link> 组件拦截了 click 事件,调用 event.preventDefault() 阻止浏览器的页面刷新,然后通过 History API(pushState)更新 URL,最后触发 React Router 的路由匹配和组件渲染。整个过程没有页面请求,是”前端路由”。<a> 标签会触发页面刷新,相当于从服务器重新请求页面,破坏了 SPA 的体验。
关联知识点索引
React 核心概念.md— 组件基础React vs Vue 对比.md— 与 Vue Router 的完整对比