前言
React Router 是 React 单页应用的核心路由库,除了基础的路由配置,日常开发中还会高频用到路由监听、编程式跳转、路由守卫等进阶功能。本文从实战角度拆解这三大核心能力,涵盖实现方式、场景对比、避坑要点,基于 React Router v6+ 版本(主流稳定版)讲解,新手也能快速落地!
一、 路由监听:如何捕捉 URL 的变化?
在 React 中,React 监听路由变化的本质是监听 URL 相关属性(pathname/search/params 等)的变化,触发自定义回调函数。以下是 3 种常用实现方式:
1. 核心方案:useLocation + useEffect
这是最通用的监听方式。通过 useLocation 获取当前路由完整信息(pathname/search/state 等),结合 useEffect 监听 location 对象变化,触发回调函数。
import { useLocation } from 'react-router-dom';
import { useEffect } from 'react';
const App = () => {
const location = useLocation();
useEffect(() => {
// 每次路由切换时执行
console.log('当前路径:', location.pathname);
console.log('搜索参数:', location.search);
}, [location]);
};
2. 精准监听:useParams 与 useSearchParams
如果你只关心某个特定的动态参数(如 id),直接监听参数对象会更高效。
-
useParams:监听动态路由参数(如/user/:id中的id); -
useSearchParams:监听 URL 搜索参数(如?page=1&size=10); -
适用场景:
- 仅关注动态路由参数或搜索参数变化的场景(如详情页 ID 切换、列表页分页参数变化)。
3.监听原生路由事件(不推荐)
-
通过
window.addEventListener监听popstate(History 模式)或hashchange(Hash 模式)事件,直接捕获 URL 变化。 -
缺点:React Router 已封装原生事件,手动监听易出现重复触发、状态不一致问题,仅建议特殊场景(如兼容老代码)使用。
二、 路由跳转
React Router 提供多种跳转方式,适配「点击跳转」「编程式跳转」「导航栏高亮」等不同场景:
1. 声明式导航:<Link> 与 <NavLink>(最常用)
-
<Link>:基础跳转,React Router 核心跳转组件,替代原生<a>标签(避免页面刷新),核心属性如下:-
to:必传,目标路径(支持字符串 / 对象格式); -
replace:默认false(新增历史记录),true则替换当前记录(跳转后无法回退); -
state:传递自定义状态(不显示在 URL 中,通过useLocation().state获取)。
-
-
<NavLink>:专为导航栏设计,新增激活状态相关配置,适合导航栏场景:-
isActive:可根据激活状态动态设置样式类名。 -
end:精准匹配模式,防止/匹配到所有子路由(如/home不匹配/home/detail)。
-
2. 编程式导航:useNavigate
适用于点击按钮后的逻辑处理或异步请求后的跳转,通过 useNavigate Hook 获取导航函数,实现非点击触发的跳转(如接口请求后、条件判断后)。
const navigate = useNavigate();
// 基础跳转,带状态
navigate('/profile', { replace: true, state: { from: 'home' } });
// 历史记录操作
navigate(-1); // 后退一步
三、 路由守卫:在 React 中如何“拦截”?
Vue 有原生的 beforeEach/afterEach 路由守卫,但 React Router 无专属 API,核心通过监听路由 + 条件判断实现,分为 3 类场景:
1. 全局路由守卫
实现逻辑
在路由根组件(如 App.jsx)中监听 location 变化,执行全局校验(如登录状态、白名单),拦截非法跳转。
实战代码
import { useLocation, useNavigate, useEffect } from 'react-router-dom';
import { isLogin } from '@/utils/auth'; // 自定义登录校验函数
// 全局路由守卫组件
const GlobalRouterGuard = () => {
const location = useLocation();
const navigate = useNavigate();
// 无需登录的白名单路由
const whiteList = ['/login', '/register'];
useEffect(() => {
// 未登录且不在白名单 → 跳转到登录页
if (!isLogin() && !whiteList.includes(location.pathname)) {
navigate('/login', {
replace: true,
state: { from: location.pathname } // 记录来源路径,登录后跳转回去
});
}
}, [location.pathname, navigate]);
return null; // 守卫组件无需渲染 DOM
};
// 在根路由中引入
// <BrowserRouter>
// <GlobalRouterGuard />
// <Routes>...</Routes>
// </BrowserRouter>
2. 组件内路由守卫
实现逻辑
在组件内通过 useEffect 实现进入守卫(组件挂载时校验),通过 useEffect 的返回函数实现离开守卫(组件卸载时执行)。
实战代码
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { hasPermission } from '@/utils/permission'; // 自定义权限校验
const UserDetail = () => {
const navigate = useNavigate();
// 进入守卫:组件挂载时校验权限
useEffect(() => {
if (!hasPermission('user:view')) {
navigate('/403', { replace: true });
}
}, [navigate]);
// 离开守卫:组件卸载时执行(如保存表单、提示未提交内容)
useEffect(() => {
return () => {
console.log('离开用户详情页,执行清理逻辑');
// 业务逻辑:保存草稿、关闭WebSocket等
};
}, []);
return <div>用户详情页</div>;
};
3. 路由独享守卫
-
概念: 不影响全局其他路由,只针对这一个页面(或这一组页面)进行特殊的准入检查。 例如整个应用都可以访问,但只有
/admin页面需要检查管理员权限 -
实现”先创建一个守卫组件
AdminGuard,这个组件专门负责检查当前用户是不是管理员,然后将需要单独检查的Admin路由套在这个守卫组件AdminGuard里面
实战代码:
// 守卫组件:AdminGuard.jsx
import { useNavigate } from 'react-router-dom';
import { isAdmin } from '@/utils/auth';
const AdminGuard = ({ children }) => {
const navigate = useNavigate();
// 管理员权限校验
if (!isAdmin()) {
navigate('/403', { replace: true });
return null;
}
// 权限通过,渲染子组件
return children;
};
// 路由配置中使用
import { Routes, Route } from 'react-router-dom';
import SettingsPage from '@/pages/Settings';
const RouterConfig = () => {
return (
<Routes>
{/* 独享守卫:仅 /settings 路由触发管理员校验 */}
<Route
path="/settings"
element={
<AdminGuard>
<SettingsPage />
</AdminGuard>
}
/>
</Routes>
);
};
4.扩展:离开时的路由拦截(useBlocker)
useBlocker 是 React Router v6 新增 Hook,用于拦截所有路由跳转行为(包括 <Link>、navigate、浏览器前进 / 后退)。
import { useBlocker } from 'react-router-dom';
// blockerFn:返回 true 拦截跳转,false 放行
// when:是否启用拦截(可选,默认 true)
useBlocker((tx) => {
console.log('即将跳转到:', tx.location.pathname);
return true; // 拦截跳转
}, when);
实战场景:表单未提交拦截
import { useBlocker } from 'react-router-dom';
const FormPage = () => {
const [formDirty, setFormDirty] = useState(false); // 表单是否修改
// 表单未提交时拦截跳转
useBlocker((tx) => {
if (formDirty) {
const confirm = window.confirm('表单内容未保存,是否确认离开?');
return !confirm; // 点击取消 → 拦截(返回 true)
}
return false; // 放行
}, formDirty); // 仅表单修改时启用拦截
return (
<form onChange={() => setFormDirty(true)}>
<input type="text" placeholder="输入内容..." />
</form>
);
};
四、 总结与最佳实践
- 优先使用原生 Link:对于简单的跳转,
<Link>的性能和 SEO 优于useNavigate。 - 善用 State 传参:如果不想 URL 变得太长,利用
location.state传递对象是最佳选择。 - 守卫逻辑模块化:不要在
App.js里写一堆if-else,将权限校验封装成独立的 Guard 组件。