React Router 是 React 生态中最核心的路由解决方案,用于实现单页应用(SPA)的路由跳转、视图切换等功能。掌握其核心用法、工作中的避坑点及面试高频考点,是前端工程师的必备技能。本文将系统梳理相关内容,帮助你全面掌握 React Router。
一、核心知识点:从基础到进阶
React Router 经历了多个版本迭代,目前主流版本为 v6(较 v5 有较大改动,需重点关注)。以下是 v6 版本的核心知识点:
1.1 核心库与安装
React Router 针对不同环境提供了多个库,日常开发主要使用以下两个:
-
react-router-dom:用于浏览器环境(Web 开发核心库); -
react-router-native:用于 React Native 移动开发。
安装命令(v6 版本):
javascript
npm install react-router-dom@6
yarn add react-router-dom@6
1.2 核心组件与作用
v6 版本精简了核心组件,取消了 v5 中的 <Switch>、<Redirect> 等,新增了 <Routes>、<Navigate> 等,核心组件及作用如下:
-
<BrowserRouter>:路由根容器,基于 HTML5 的 history API 实现路由(URL 无 # 号),需包裹整个应用; -
<HashRouter>:基于 URL 的 hash 实现路由(URL 含 # 号),兼容低版本浏览器,部署简单; -
<Routes>:替代 v5 的<Switch>,用于包裹多个<Route>,匹配到第一个符合条件的路由后停止匹配; -
<Route>:定义路由规则,通过path属性指定路由路径,element属性指定对应渲染组件; -
<Link>:替代原生<a>标签,实现无刷新路由跳转,通过to属性指定目标路径; -
<NavLink>:<Link>的增强版,支持路由激活状态(可通过className配置激活样式); -
<Navigate>:替代 v5 的<Redirect>,实现路由重定向,通过to属性指定重定向目标; -
<Outlet>:用于嵌套路由,在父路由组件中渲染子路由组件的占位符。
1.3 基础路由实现(v6)
最基础的路由配置示例,实现多页面无刷新跳转:
javascript
// 入口文件 index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter as Router } from 'react-router-dom';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Router>
<App />
</Router>
);
// App.js
import { Routes, Route, Link } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';
function App() {
return (
<div>
{/* 导航栏 */}
<nav>
<Link to="/" style={{ marginRight: '10px' }}>首页</Link>
<Link to="/about" style={{ marginRight: '10px' }}>关于我们</Link>
<Link to="/contact">联系我们</Link>
</nav>
{/* 路由匹配区域 */}
<Routes>
<Route path="/" element={<Home />} /> {/* 首页路由 */}
<Route path="/about" element={<About />} /> {/* 关于我们路由 */}
<Route path="/contact" element={<Contact />} /> {/* 联系我们路由 */}
</Routes>
</div>
);
}
1.4 进阶路由特性
1.4.1 动态路由与参数获取
用于匹配动态路径(如详情页),通过 :参数名 定义动态参数,使用 useParams() 钩子获取参数:
动态参数传参的适用场景和注意事项
适用场景:详情页(用户详情、商品详情等,需要通过唯一ID定位数据)
注意1:参数会显示在URL中,不适合传递敏感数据(如密码)
注意2:路由配置要写在更具体的路由后面,避免冲突(如先写/user/list,再写/user/:id)
javascript
// 一、动态参数传参(补充完整:配置+传参+接收)
// 1. App.js 中配置动态路由:用 :参数名 定义占位符(这里是:id)
<Routes>
// 动态路由:匹配 /user/101、/user/102 等路径
<Route path="/user/:id" element={<UserDetail />} />
</Routes>
// 2. 发起动态参数传参(两种方式)
// 方式A:通过Link组件跳转传参(声明式导航)
// 在任意组件中使用Link,to属性直接拼接参数
import { Link } from 'react-router-dom';
function UserList() {
return (
<div>
{/* 跳转到用户101的详情页,参数101拼在路径中 */}
<Link to="/user/101">查看用户101详情</Link>
</div>
);
}
// 方式B:通过编程式导航传参(就是上面Login组件中用的navigate)
// navigate(`/user/${userInfo.id}`); // 拼接id参数
// 3. UserDetail.js 中获取动态参数
import { useParams } from 'react-router-dom';
function UserDetail() {
// 调用useParams(),结构赋值取出id(参数名和路由配置的:id一致)
const { id } = useParams();
// 重要:useParams()获取的参数都是字符串类型!如果需要数字,要手动转:Number(id)
return <div>
<h3>用户详情页</h3>
<p>用户ID:{id}(类型:{typeof id})</p>
<p>用户ID(数字):{Number(id)}(类型:{typeof Number(id)})</p>
</div>;
}
1.4.2 嵌套路由
实现路由的层级嵌套(如父页面包含子页面),通过 children 属性配置子路由,配合 <Outlet> 渲染子组件:
javascript
// App.js 中配置嵌套路由
<Routes>
<Route path="/dashboard" element={<Dashboard />} >
<Route index element={<DashboardHome />} /> {/* 子路由默认页(index 表示默认匹配) */}
<Route path="profile" element={<UserProfile />} /> {/* 子路由:/dashboard/profile */}
<Route path="settings" element={<Settings />} /> {/* 子路由:/dashboard/settings */}
</Route>
</Routes>
// Dashboard.js 中使用 Outlet 渲染子组件
import { Outlet, Link } from 'react-router-dom';
function Dashboard() {
return (
<div>
<h1>控制台</h1>
<nav>
<Link to="profile" style={{ marginRight: '10px' }}>个人资料</Link>
<Link to="settings">设置</Link>
</nav>
<Outlet /> {/* 子路由组件的占位符,渲染匹配的子组件 */}
</div>
);
}
1.4.3 编程式导航
通过 API 实现路由跳转(如按钮点击后跳转),使用 useNavigate() 钩子获取导航函数:
javascript
// 1. 导入编程式导航所需的钩子:useNavigate
// 初学者注意:这个钩子只能在 React 函数组件内部使用
import { useNavigate } from 'react-router-dom';
// 登录组件(普通函数组件)
function Login() {
// 2. 调用 useNavigate() 得到导航函数,命名为 navigate(名字可自定义)
const navigate = useNavigate();
// 3. 登录按钮点击事件处理函数(模拟登录逻辑)
const handleLogin = () => {
// 模拟登录验证:实际项目中这里会调用后端登录接口,验证账号密码
const isLoginSuccess = true; // 假设登录成功(初学者可先固定为true测试)
if (isLoginSuccess) {
// 模拟后端返回的用户数据(含token和用户信息)
const userInfo = {
token: 'fake-token-123456',
id: 101,
name: '张三'
};
// 存储token(供受保护路由判断权限)
localStorage.setItem('token', userInfo.token);
// -------------- 路由传参详解(3种核心方式)--------------
// 方式1:动态参数传参(适合传递简单标识,如ID,显示在URL中)
// 适用场景:详情页(如用户详情、商品详情)
navigate(`/user/${userInfo.id}`); // 跳转路径:/user/101
// 方式2:state参数传参(隐式传参,不显示在URL中,适合传递复杂数据)
// 适用场景:页面间传递敏感数据、复杂对象(如用户信息)
// 注意:state参数刷新页面后会丢失,需持久化可存localStorage
// navigate('/dashboard', {
// state: { user: userInfo } // 要传递的复杂数据
// });
// 方式3:查询参数传参(显示在URL中,格式:?key=value,适合传递可选参数)
// 适用场景:列表页筛选、分页等(如?page=1&size=10)
// navigate('/userList?page=1&size=10');
// 补充:后退/前进页面(类似浏览器的前进后退按钮)
// navigate(-1); // 后退1页
// navigate(1); // 前进1页
} else {
// 登录失败的逻辑:比如提示"账号密码错误"
alert("登录失败,请检查账号密码!");
}
};
// 4. 渲染登录按钮,绑定点击事件
return <button onClick={handleLogin}>登录</button>;
}
// ---------------- 对应传参方式的接收示例 ----------------
// 示例1:接收【动态参数】(对应方式1,以UserDetail组件为例)
import { useParams } from 'react-router-dom';
function UserDetail() {
// 用useParams()获取动态参数,结构赋值取出id
const { id } = useParams();
// 注意:获取到的参数都是字符串类型,若需要数字需手动转换(如 Number(id))
return <div>
<p>用户详情页</p>
<p>用户ID:{id}</p>
</div>;
}
// 示例2:接收【state参数】(对应方式2,以Dashboard组件为例)
import { useLocation } from 'react-router-dom';
function Dashboard() {
// 用useLocation()获取路由相关信息,其中state就是传递的参数
const location = useLocation();
const { user } = location.state || {}; // 加||{}避免未传参时报错
return <div>
<p>控制台</p>
<p>欢迎您:{user?.name}(ID:{user?.id})</p>
<Outlet /> {/* 子路由占位符 */}
</div>;
}
// 示例3:接收【查询参数】(对应方式3,以UserList组件为例)
import { useSearchParams } from 'react-router-dom';
function UserList() {
// useSearchParams()返回数组:第一个是参数对象,第二个是修改参数的函数
const [searchParams, setSearchParams] = useSearchParams();
// 获取单个查询参数:get(参数名),返回字符串
const page = searchParams.get('page') || 1; // 无参数时默认1
const size = searchParams.get('size') || 10;
// 修改查询参数(示例:切换到第2页)
const goToPage2 = () => {
setSearchParams({ page: 2, size: 10 }); // 会自动拼接成 ?page=2&size=10
};
return <div>
<p>用户列表页 - 第{page}页,每页{size}条</p>
<button onClick={goToPage2}>跳转到第2页</button>
</div>;
}
1.4.4 路由守卫(权限控制)
初学者重点理解:路由守卫就是"路由的大门守卫",用来控制哪些人能访问某个路由。v6 版本取消了 v5 里复杂的配置方式,改用更简单的"受保护路由组件"实现,核心逻辑是:
-
先判断用户是否有权限(比如是否登录);
-
有权限就放行,显示目标页面;
-
没权限就拦住,强制跳转到登录页。
补充:路由守卫与路由传参的关联:登录后跳转时传递的用户信息(state参数),在受保护路由的目标组件(如Dashboard)中可以直接接收,无需重复请求接口,简化数据传递流程。
二、工作实际注意点
1.4.5 常用API详解
React Router v6 提供了多个实用的钩子函数(API),用于获取路由信息、实现导航、操作参数等,以下是开发中最常用的5个核心API,均支持在函数组件中直接使用:
1. useParams() ------ 获取动态路由参数
作用 :获取路由配置中定义的动态参数(如 /user/:id 中的 id),适用于详情页等需要通过唯一标识定位数据的场景。
语法 :const { 参数名1, 参数名2 } = useParams();
示例(延续用户详情页场景):
javascript
import { useParams } from 'react-router-dom';
function UserDetail() {
// 1. 结构赋值取出动态参数id(参数名必须和路由配置的:id一致)
const { id } = useParams();
// 2. 注意:获取到的参数是字符串类型,若需数字需手动转换
const userId = Number(id);
return <div>
<h3>用户详情页</h3>
<p>动态参数id(字符串):{id}</p>
<p>转换后id(数字):{userId}</p>
</div>;
}
注意事项 :参数名必须与 <Route path="/user/:id"> 中 : 后面的名称完全一致,否则无法获取;参数默认是字符串,数字类参数需手动转换。
2. useSearchParams() ------ 获取/修改查询参数
作用 :操作 URL 中 ? 后面的查询参数(如 /userList?page=1&size=10),支持获取参数和修改参数,适用于列表页筛选、分页等场景。
语法 :const [searchParams, setSearchParams] = useSearchParams();(类似 useState 的数组结构,第一个参数是参数对象,第二个是修改函数)
示例(延续用户列表分页场景):
javascript
import { useSearchParams } from 'react-router-dom';
function UserList() {
// 1. 获取查询参数对象和修改函数
const [searchParams, setSearchParams] = useSearchParams();
// 2. 获取单个参数:get(参数名),无参数时返回null,需设置默认值
const page = searchParams.get('page') || 1; // 默认第1页
const size = searchParams.get('size') || 10; // 默认每页10条
// 3. 修改查询参数:调用setSearchParams,自动拼接URL
const goToPage2 = () => {
// 传对象:会覆盖原有查询参数(仅保留page和size)
setSearchParams({ page: 2, size: 10 });
// 若需保留原有参数,可先获取再修改:
// setSearchParams(prev => {
// prev.set('page', 2);
// return prev;
// });
};
return <div>
<h3>用户列表页</h3>
<p>当前页码:{page},每页条数:{size}</p>
<button onClick={goToPage2}>跳转到第2页</button>
</div>;
}
注意事项:get() 方法返回的是字符串,数字参数需手动转换;setSearchParams() 传对象会覆盖原有查询参数,保留原有参数需用回调函数修改。
3. useNavigate() ------ 编程式导航
作用:实现通过代码触发路由跳转(如按钮点击、登录成功后跳转),替代 v5 中的 useHistory(),功能更强大。
语法 :const navigate = useNavigate();(调用后返回导航函数)
核心用法:
-
基础跳转:
navigate(目标路径)(如navigate('/dashboard')) -
带参数跳转:
navigate(目标路径, { state: 数据 })(state参数隐式传递,不显示在URL) -
前进/后退:
navigate(数字)(如navigate(-1)后退1页,navigate(1)前进1页) -
替换历史记录:
navigate(目标路径, { replace: true })(避免回退到当前页,适用于重定向)
示例(登录跳转场景):
javascript
import { useNavigate } from 'react-router-dom';
function Login() {
const navigate = useNavigate();
const handleLogin = () => {
const loginSuccess = true; // 模拟登录成功
if (loginSuccess) {
const userInfo = { name: '张三', id: 101 };
// 1. 带state参数跳转,隐式传递用户信息
navigate('/dashboard', {
state: userInfo,
replace: true // 替换历史记录,避免回退到登录页
});
}
};
return <button onClick={handleLogin}>登录</button>;
}
注意事项:该钩子只能在函数组件内部使用;state参数刷新页面后会丢失,敏感数据需配合localStorage持久化。
4. useLocation() ------ 获取路由位置信息
作用:获取当前路由的完整位置信息(如路径、搜索参数、state参数等),适用于需要获取当前路由状态的场景(如接收state参数、监听路由变化)。
语法 :const location = useLocation();
核心属性:
-
location.pathname:当前路由路径(如
/dashboard) -
location.search:查询参数字符串(如
?page=1&size=10) -
location.state:通过 navigate 传递的 state 参数(隐式参数)
-
location.hash:URL 中的 hash 值(如
#section1)
示例(接收state参数场景):
javascript
import { useLocation } from 'react-router-dom';
import { Outlet } from 'react-router-dom';
function Dashboard() {
// 1. 获取路由位置信息
const location = useLocation();
// 2. 接收登录页传递的state参数(加||{}避免未传参时报错)
const user = location.state || {};
return <div>
<h3>控制台</h3>
<p>当前路径:{location.pathname}</p>
<p>欢迎您:{user.name || '未登录用户'}</p>
<Outlet /> {/* 子路由占位符 */}
</div>;
}
注意事项 :location 对象会随着路由变化而更新,可配合 useEffect 监听路由变化(如 useEffect(() => { ... }, [location]))。
5. useRoutes() ------ 编程式配置路由
作用 :用 JavaScript 数组配置路由规则,替代 JSX 格式的 <Routes> 和 <Route>,适用于路由规则较复杂、需要动态生成路由的场景。
语法 :const element = useRoutes(路由规则数组);
示例(替代 App.js 中的 JSX 路由配置):
javascript
import { useRoutes } from 'react-router-dom';
import Home from './pages/Home';
import Login from './pages/Login';
import Dashboard from './pages/Dashboard';
import UserDetail from './pages/UserDetail';
import ProtectedRoute from './components/ProtectedRoute';
function App() {
// 1. 定义路由规则数组(和JSX配置一一对应)
const routes = [
{ path: '/', element: <Home /> }, // 首页路由
{ path: '/login', element: <Login /> }, // 登录路由
// 受保护路由组:element指定受保护组件,children是子路由
{
element: <ProtectedRoute />,
children: [
{ path: '/dashboard', element: <Dashboard /> },
{ path: '/user/:id', element: <UserDetail /> }
]
}
];
// 2. 调用useRoutes生成路由组件
const routeElement = useRoutes(routes);
return (
<div>
{/* 导航栏(和之前一致) */}
<nav>
<Link to="/" style={{ marginRight: '10px' }}>首页</Link>
<Link to="/login" style={{ marginRight: '10px' }}>登录</Link>
<Link to="/dashboard">控制台</Link>
</nav>
{/* 渲染路由组件 */}
{routeElement}
</div>
);
}
注意事项:路由规则数组的结构和 JSX 配置完全一致,只是用对象格式描述;动态路由、嵌套路由、受保护路由的配置逻辑和 JSX 方式相同,适合复杂项目统一管理路由规则。
在实际项目开发中,React Router 的使用需关注以下细节,避免踩坑:
1.4.6 RouterProvider(基于数据的路由)
RouterProvider 是 React Router v6.4+ 版本引入的核心组件,用于实现"基于数据的路由"(Data Router),替代了传统的<BrowserRouter>+<Routes> 组合。它通过 createBrowserRouter 等函数创建路由配置,支持路由加载数据(loader)、提交数据(action)、错误处理等高级功能,是 v6 后期推荐的路由配置方式。
核心作用:
-
统一管理路由规则:用数组配置所有路由,替代分散的 JSX 路由标签;
-
内置高级功能:支持路由级别的数据加载/提交、错误边界、延迟加载等,无需额外配置;
-
更灵活的路由控制:可动态修改路由配置,适配复杂项目需求。
核心用法(三步实现):
第一步:理解核心依赖与概念
-
createBrowserRouter:创建浏览器环境的路由实例,接收路由配置数组,返回可传递给 RouterProvider 的路由对象; -
RouterProvider:路由根容器,接收router属性(即 createBrowserRouter 的返回值),替代传统的<BrowserRouter>; -
路由配置数组:每个元素是路由对象,包含
path(路由路径)、element(渲染组件)、children(子路由)等属性。
第二步:完整示例(替代传统路由配置)
javascript
// 1. 导入核心依赖(v6.4+ 版本才支持,需确保安装对应版本)
App.tsx根节点:
import { RouterProvider } from 'react-router-dom'
import { router } from '@/router'
export default function App(): ReactElement {
return (
<Suspense fallback={<div style={{ padding: 16 }}></div>}>
<RouterProvider router={router} />
</Suspense>
)
}
// 2. 定义路由配置数组(和 useRoutes 配置类似,支持嵌套、受保护路由)
index.tsx(router)
import { createBrowserRouter } from 'react-router-dom'
import { routes } from './routes'
routes.tsx
import { lazy } from 'react'
import type { RouteObject } from 'react-router-dom'
import AppLayout from '@/layouts/AppLayout'
//未登录不能访问的页面
import AuthGuard from './AuthGuard'
const Login = lazy(() => import('@/pages/Login/index'))
const NotFound = lazy(() => import('@/pages/NotFound'))
const WorkInfo = lazy(() => import('@/pages/Apply/WorkInfo'))
const ContactsInfo = lazy(() => import('@/pages/Apply/ContactsInfo'))
const PersonalInfo = lazy(() => import('@/pages/Apply/PersonalInfo'))
const IdInfo = lazy(() => import('@/pages/Apply/IdInfo'))
const FaceCapture = lazy(() => import('@/pages/Apply/FaceCapture'))
const BankInfo = lazy(() => import('@/pages/Apply/BankInfo'))
export const routes: RouteObject[] = [
// 登录页
{ path: '/login', element: <Login /> },
{
// 路由守卫
element: <AuthGuard />,
children: [
{ path: 'work', element: <WorkInfo /> },
{ path: 'contacts', element: <ContactsInfo /> },
{ path: 'personal', element: <PersonalInfo /> },
{ path: 'id', element: <IdInfo /> },
{ path: 'face-capture', element: <FaceCapture /> },
{ path: 'bank', element: <BankInfo /> },
],
},
// 404 页面
{ path: '*', element: <NotFound /> },
]
AuthGuard.tsx 权限管理
import { type ReactElement } from 'react'
import { Navigate, Outlet } from 'react-router-dom'
import { getStorage, StorageKeys } from '@/utils/storage'
export default function AuthGuard(): ReactElement {
// 检查登录状态
const loginInfo = getStorage(StorageKeys.LOGIN_INFO)
if (!loginInfo) {
// 未登录跳转至登录页
return <Navigate to="/login" replace />
}
// 已登录显示子路由
return <Outlet />
}
第三步:注意事项
-
版本要求:RouterProvider 是 v6.4+ 新增特性,需确保安装的 react-router-dom 版本 ≥ 6.4.0(可通过
npm list react-router-dom查看版本); -
与传统路由的区别:RouterProvider 是"基于数据的路由"核心,替代了
<BrowserRouter>+<Routes>,但路由跳转(Link、useNavigate)、参数获取(useParams 等)API 完全兼容旧用法; -
高级功能入门:RouterProvider 支持
loader(路由进入前加载数据)、action(表单提交等操作)、errorElement(路由错误处理)等高级功能,初学者可先掌握基础配置,后续再深入; -
适用场景:小型项目用传统路由或 useRoutes 即可,中大型项目推荐用 RouterProvider,便于统一管理路由和数据逻辑。
补充:RouterProvider 与 useRoutes 的区别
-
相同点:都支持用数组配置路由规则,核心配置项(path、element、children)一致;
-
不同点:RouterProvider 是组件,需作为根容器包裹应用,支持 loader/action 等高级功能;useRoutes 是钩子函数,需在组件内部调用,返回路由组件,不支持高级数据功能。
2.1 路由模式选择(BrowserRouter vs HashRouter)
-
BrowserRouter(history 模式):URL 美观(无 # 号),但需要后端配置支持(解决刷新 404 问题)。部署时,后端需将所有路由请求转发到 index.html(如 Nginx 配置 try_files uri uri/ /index.html;);
-
HashRouter(hash 模式):URL 含 # 号,兼容性好,无需后端配置,适合静态页面部署(如 GitHub Pages)。但 # 号后的内容不会发送到服务器,可能影响 SEO。
注意:若项目需要 SEO 优化,优先选择 BrowserRouter,并配合后端配置;静态页面部署优先选择 HashRouter。
2.2 避免路由冲突
-
路由匹配顺序:
<Routes>会按顺序匹配路由,需将更具体的路由放在前面(如/user/:id需放在/user/list后面,否则/user/list会被匹配为/user/:id,id 为 list); -
使用
exact(v5)/ 嵌套路由(v6):v6 中<Route>默认是"精确匹配"的简化版,通过嵌套路由和index路由可避免冲突。
2.3 路由懒加载与代码分割
大型项目中,为优化首屏加载速度,需对路由组件进行懒加载(仅当访问该路由时才加载组件代码),配合 React 的 React.lazy() 和 <Suspense> 实现:
javascript
import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';
// 懒加载路由组件(动态导入)
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
function App() {
return (
<Suspense fallback={<div>加载中...</div>} > {/* 加载过程中显示的占位内容 */}
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
);
}
2.4 路由参数与状态管理
-
路由传参方式:
-
动态参数(
/user/:id):适合传递简单标识(如 ID),参数会显示在 URL 中; -
state 参数(
navigate('/user', { state: { name: '张三' } })):适合传递复杂数据,参数不会显示在 URL 中,刷新页面后会丢失(需配合本地存储持久化); -
查询参数(
/user?name=张三):适合传递可选参数,使用useSearchParams()钩子获取和修改。
-
-
避免路由参数过度使用:复杂状态(如用户信息、全局配置)建议使用 Redux、Context 等状态管理工具,而非路由参数。
2.5 404 页面配置
通过 path="*" 匹配所有未定义的路由,实现 404 页面:
javascript
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="*" element={<NotFound />} /> {/* 404 页面,匹配所有未定义的路由 */}
</Routes>
三、面试重点:高频问题与核心答案
React Router 是前端面试的高频考点,以下是常见问题及精准回答思路:
3.1 问题 1:React Router 的核心原理是什么?
核心答案:
-
React Router 基于"单页应用(SPA)"的核心思想,通过监听 URL 的变化,在不刷新页面的前提下,动态切换渲染对应的组件;
-
底层依赖两种 URL 变化监听方式:
-
history 模式(BrowserRouter):基于 HTML5 的
history.pushState()和history.replaceState()API,可修改 URL 而不触发页面刷新,通过监听popstate事件处理浏览器前进/后退; -
hash 模式(HashRouter):基于 URL 的 hash(# 号后的内容),通过监听
hashchange事件感知 hash 变化,进而切换组件。
-
3.2 问题 2:React Router v5 和 v6 有哪些主要区别?
核心答案(重点区别):
-
路由容器组件:v5 使用
<Switch>匹配路由,v6 替换为<Routes>,匹配逻辑更高效(匹配到第一个即停止); -
Route 组件属性:v5 使用
component、render属性指定组件,v6 统一使用element属性(需传递组件实例,如element={<Home />}); -
嵌套路由:v5 需手动拼接父路由路径,v6 支持嵌套路由配置(
children属性),配合<Outlet>渲染子组件,更简洁; -
编程式导航:v5 使用
useHistory()钩子,v6 替换为useNavigate()钩子,功能更强大(支持前进/后退、替换历史记录等); -
重定向:v5 使用
<Redirect>组件,v6 替换为<Navigate>组件,通过replace属性实现重定向。
3.3 问题 3:如何实现 React Router 的权限控制(路由守卫)?
核心答案(v6 方案):
-
实现思路:创建"受保护路由组件",在组件内部判断用户权限(如是否登录、是否有角色权限),根据权限决定渲染子组件或重定向到登录页;
-
核心实现:利用
<Outlet>组件渲染子路由,未授权时使用<Navigate>重定向; -
扩展:可根据角色实现更精细的权限控制(如管理员才能访问
/admin路由),在受保护组件中添加角色判断逻辑。
3.4 问题 4:history 模式和 hash 模式的区别,以及 history 模式部署时的注意事项?
核心答案:
-
区别:
-
URL 外观:history 模式无 # 号,hash 模式有 # 号;
-
底层原理:history 模式基于 HTML5 history API,hash 模式基于 hashchange 事件;
-
后端依赖:history 模式需要后端配置,hash 模式无需后端配置;
-
SEO 影响:history 模式更利于 SEO(URL 更规范),hash 模式 # 号后内容不被搜索引擎抓取。
-
-
history 模式部署注意事项:后端需将所有路由请求转发到 index.html(如 Nginx 配置 try_files uri uri/ /index.html;),否则刷新页面会出现 404 错误(因为浏览器会将路由路径当作真实接口请求后端)。
3.5 问题 5:如何实现路由懒加载?
核心答案:
-
React Router 结合 React 内置的
React.lazy()和<Suspense>实现路由懒加载; -
核心逻辑:
React.lazy()接收一个动态导入函数(() => import('./pages/Home')),返回一个懒加载组件;<Suspense>组件用于包裹懒加载组件,指定加载过程中的占位内容(fallback属性); -
注意:懒加载组件只能用于
<Suspense>内部,否则会报错。
3.6 问题 6:useParams、useSearchParams、useNavigate 各自的作用?
核心答案:
-
useParams():用于获取动态路由参数(如/user/:id中的 id),返回一个包含参数的对象; -
useSearchParams():用于获取和修改 URL 中的查询参数(如/user?name=张三),返回一个数组,第一个元素是SearchParams对象(可通过get()方法获取参数),第二个元素是修改查询参数的函数; -
useNavigate():用于实现编程式导航,返回一个导航函数,可通过传递路径和配置对象实现跳转、重定向、前进/后退等功能。
四、总结
React Router 是 React 单页应用的核心路由解决方案,需重点掌握 v6 版本的核心组件(<Routes>、<Route>、<Outlet> 等)、动态路由、嵌套路由、编程式导航、权限控制等核心功能。工作中需注意路由模式选择、路由冲突避免、懒加载优化、404 页面配置等细节;面试中则需深入理解其实现原理、v5 与 v6 的区别、权限控制实现方式等核心考点。
建议结合实际项目多练习,尤其是嵌套路由和权限控制场景,加深对 React Router 用法和原理的理解。