在构建单页应用(SPA)时,前端路由是核心骨架。我们习惯了用 <Link> 跳转页面,用 <Route> 定义路径。但在复杂的业务场景中,仅仅会配置路由是远远不够的。
你是否遇到过这样的场景:
- 用户未登录时点击“个人中心”,跳转登录页,登录成功后却只能回到首页,而不是刚才想去的“个人中心”?
- 表单提交后,用户刷新页面导致数据重复提交?
- 在复杂的嵌套路由中,搞不清当前的 URL 参数到底在哪里?
解决这些问题的关键,就在于深入理解 React Router 提供的两个核心 Hooks:useLocation 和 useNavigate。它们不仅是跳转和获取参数的工具,更是掌控用户浏览历史、实现丝滑鉴权体验的“核武器”。
本文将结合实战代码,带你彻底吃透这两个 API,从原理到最佳实践,让你的路由控制能力上一个台阶。
📍 一、 useLocation:不仅仅是获取 URL 参数
很多开发者对 useLocation 的理解仅停留在“获取查询参数(Query Params)”上。实际上,它是当前激活路由的状态快照。
1. location 对象的全貌
当你在组件中调用 const location = useLocation() 时,你拿到的是一个包含以下属性的对象:
// 假设当前 URL 为: http://127.0.0.1:5500/#/login?redirect=%2Fuser%2F123
{
pathname: "/login", // 路由路径(不含查询参数)
search: "?redirect=%2Fuser%2F123", // 查询字符串
hash: "", // hash 值(在 HashRouter 中通常为空,因为路径已被解析)
state: null, // 关键!这是通过 navigate 传递的“隐式”数据
key: "xs63g7" // 唯一标识,用于跟踪历史记录条目
}
2. 实战:解析 URL 参数
最基础的用法是解析 search 字段。虽然我们可以手写正则,但现代浏览器原生提供了 URLSearchParams,结合 useLocation 使用非常优雅:
import { useLocation } from 'react-router-dom';
const SearchPage = () => {
const { search } = useLocation();
// 自动处理 ? 符号,解析为键值对
const params = new URLSearchParams(search);
const query = params.get('q'); // 获取 ?q=xxx 中的值
return <div>搜索关键词:{query}</div>;
};
🚀 二、 useNavigate:掌控跳转的“方向盘”
useNavigate 取代了 v5 版本的 history.push,它更加灵活,支持命令式导航。
1. 基础跳转与参数传递
import { useNavigate } from 'react-router-dom';
const MyComponent = () => {
const navigate = useNavigate();
const goToDetail = () => {
// 1. 字符串跳转
navigate('/detail/123');
// 2. 数字跳转(类似 history.go)
// navigate(-1); // 后退一页
};
return <button onClick={goToDetail}>查看详情</button>;
};
2. 核心秘籍:replace 模式
这是很多新手容易忽略,但在实际开发中至关重要的配置。
场景: 用户登录。
如果用户访问 /pay(支付页),被拦截跳转到 /login。登录成功后,如果使用默认的 navigate('/pay'),浏览器的历史记录栈会变成:[/pay, /login, /pay]。
此时,用户在支付页点击“后退”,会回到登录页!这显然是不合理的。
解决方案: 使用 replace: true。
它会用新页面替换当前页面在历史记录中的位置。
// Login.jsx
const handleLogin = () => {
// 模拟登录成功
localStorage.setItem('isLogin', 'true');
// 第二个参数配置 replace: true
// 这会将 /login 的记录替换为 /pay,栈变为 [/pay, /pay] -> 实际上看起来像直接跳转
navigate('/pay', { replace: true });
};
对比图解:
表格
| 模式 | 历史栈变化 (假设原栈:[A]) | 用户点击“后退”的行为 |
|---|---|---|
| 默认 (Push) | [A, B, C] | C -> B -> A |
| Replace | [A, C] (B被C替换) | C -> A |
🛡️ 三、 终极实战:实现“登录后重定向回原页面”
这是前端鉴权中最经典的需求。用户未登录访问 /user/123,跳转登录,登录后自动回到 /user/123。
这需要 useLocation 的 state 和 useNavigate 的配合。
1. 第一步:受保护路由 (ProtectedRoute)
我们需要一个高阶组件或包装组件,在渲染子组件前检查登录状态。如果未登录,跳转至登录页,并携带当前路径信息。
// ProtectedRoute.jsx
import { Navigate, useLocation } from 'react-router-dom';
const ProtectedRoute = ({ children }) => {
const isLogin = localStorage.getItem('isLogin') === 'true';
const location = useLocation(); // 获取当前试图访问的位置
if (!isLogin) {
// 未登录,重定向到登录页
// 关键点:通过 state 传递 from 信息
return (
<Navigate
to="/login"
state={{ from: location }} // 把“我从哪里来”告诉登录页
replace // 替换掉当前的访问记录,避免死循环
/>
);
}
return children;
};
export default ProtectedRoute;
2. 第二步:登录页 (Login)
登录页需要读取 state,知道用户原本想去哪。如果 state 为空(比如用户直接访问登录页),则默认跳回首页。
// Login.jsx
import { useNavigate, useLocation } from 'react-router-dom';
const Login = () => {
const navigate = useNavigate();
const location = useLocation();
// 核心逻辑:获取来源路径,如果没有则默认为 '/'
// location.state?.from?.pathname 获取原本想去的地址
const from = location.state?.from?.pathname || '/';
const handleSubmit = (e) => {
e.preventDefault();
// ... 模拟登录请求 ...
// 登录成功
localStorage.setItem('isLogin', 'true');
// 跳转回来源页面,并使用 replace 清除登录页的历史记录
navigate(from, { replace: true });
};
return (
<form onSubmit={handleSubmit}>
<h1>请登录</h1>
{/* 表单内容 */}
<button type="submit">登录</button>
</form>
);
};
export default Login;
3. 第三步:路由配置
在 App 组件中,将需要鉴权的页面包裹在 ProtectedRoute 中。
// App.jsx
import { Routes, Route } from 'react-router-dom';
import ProtectedRoute from './ProtectedRoute';
import Pay from './pages/Pay';
import Login from './pages/Login';
const App = () => {
return (
<Routes>
<Route path="/login" element={<Login />} />
{/* 需要鉴权的路由 */}
<Route
path="/pay"
element={
<ProtectedRoute>
<Pay />
</ProtectedRoute>
}
/>
</Routes>
);
};
🧠 四、 深度解析:为什么 state 比 search 参数更好?
你可能会问: “我能不能把来源路径拼在 URL 后面,比如 /login?redirect=/pay?”
答案是可以,但使用 location.state 有以下优势:
- URL 整洁:
state是隐式传递的,不会污染地址栏,用户刷新页面时(如果是编程式导航),参数依然存在于 history state 中(但在强刷时会丢失,需注意)。 - 类型安全:
state可以传递对象、布尔值等复杂数据,而search只能是字符串。 - 语义清晰:
state专用于页面间的数据传递,search专用于资源过滤和排序。
注意: location.state 的数据在用户强制刷新页面(F5)后会丢失。因此,对于极其重要的数据(如支付订单号),建议还是通过 URL 参数或后端接口获取,但对于“从哪来回哪去”这种导航辅助信息,state 是最佳选择。
📌 五、 总结与最佳实践
- 鉴权跳转必用
replace:无论是从ProtectedRoute跳往Login,还是Login跳回原页面,都建议使用{ replace: true },避免用户点击后退时陷入“登录页死循环”。 - 利用
state传递上下文:不要把useLocation仅仅当作获取?id=1的工具,利用state在页面间传递来源信息,能极大提升用户体验。 - 默认回退路径:在处理
location.state?.from时,永远要准备一个兜底路径(通常是/或/dashboard),防止用户直接访问登录页导致跳转错误。 - 区分
HashRouter与BrowserRouter:虽然 API 一致,但在HashRouter中,location.pathname不包含#后的部分,且location.hash通常为空(被 React Router 内部消费了),这点在打印日志时要注意。
掌握 useLocation 和 useNavigate,你就掌握了 React 应用的“导航系统”。希望这篇文章能帮你写出更健壮、体验更流畅的路由逻辑!