从“迷路”到“瞬移”:彻底搞懂 React Router 的 `useLocation` 与 `useNavigate`

在构建单页应用(SPA)时,前端路由是核心骨架。我们习惯了用 <Link> 跳转页面,用 <Route> 定义路径。但在复杂的业务场景中,仅仅会配置路由是远远不够的。

你是否遇到过这样的场景:

  • 用户未登录时点击"个人中心",跳转登录页,登录成功后却只能回到首页,而不是刚才想去的"个人中心"?
  • 表单提交后,用户刷新页面导致数据重复提交?
  • 在复杂的嵌套路由中,搞不清当前的 URL 参数到底在哪里?

解决这些问题的关键,就在于深入理解 React Router 提供的两个核心 Hooks:useLocationuseNavigate。它们不仅是跳转和获取参数的工具,更是掌控用户浏览历史、实现丝滑鉴权体验的"核武器"。

本文将结合实战代码,带你彻底吃透这两个 API,从原理到最佳实践,让你的路由控制能力上一个台阶。


📍 一、 useLocation:不仅仅是获取 URL 参数

很多开发者对 useLocation 的理解仅停留在"获取查询参数(Query Params)"上。实际上,它是当前激活路由的状态快照

1. location 对象的全貌

当你在组件中调用 const location = useLocation() 时,你拿到的是一个包含以下属性的对象:

perl 复制代码
// 假设当前 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 使用非常优雅:

javascript 复制代码
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. 基础跳转与参数传递

javascript 复制代码
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

它会用新页面替换当前页面在历史记录中的位置。

javascript 复制代码
// 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

这需要 useLocationstateuseNavigate 的配合。

1. 第一步:受保护路由 (ProtectedRoute)

我们需要一个高阶组件或包装组件,在渲染子组件前检查登录状态。如果未登录,跳转至登录页,并携带当前路径信息

javascript 复制代码
// 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 为空(比如用户直接访问登录页),则默认跳回首页。

javascript 复制代码
// 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 中。

javascript 复制代码
// 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>
  );
};

你可能会问: "我能不能把来源路径拼在 URL 后面,比如 /login?redirect=/pay?"

答案是可以,但使用 location.state 有以下优势:

  1. URL 整洁state 是隐式传递的,不会污染地址栏,用户刷新页面时(如果是编程式导航),参数依然存在于 history state 中(但在强刷时会丢失,需注意)。
  2. 类型安全state 可以传递对象、布尔值等复杂数据,而 search 只能是字符串。
  3. 语义清晰state 专用于页面间的数据传递,search 专用于资源过滤和排序。

注意: location.state 的数据在用户强制刷新页面 (F5)后会丢失。因此,对于极其重要的数据(如支付订单号),建议还是通过 URL 参数或后端接口获取,但对于"从哪来回哪去"这种导航辅助信息,state 是最佳选择。


📌 五、 总结与最佳实践

  1. 鉴权跳转必用 replace :无论是从 ProtectedRoute 跳往 Login,还是 Login 跳回原页面,都建议使用 { replace: true },避免用户点击后退时陷入"登录页死循环"。
  2. 利用 state 传递上下文 :不要把 useLocation 仅仅当作获取 ?id=1 的工具,利用 state 在页面间传递来源信息,能极大提升用户体验。
  3. 默认回退路径 :在处理 location.state?.from 时,永远要准备一个兜底路径(通常是 //dashboard),防止用户直接访问登录页导致跳转错误。
  4. 区分 HashRouterBrowserRouter :虽然 API 一致,但在 HashRouter 中,location.pathname 不包含 # 后的部分,且 location.hash 通常为空(被 React Router 内部消费了),这点在打印日志时要注意。

掌握 useLocationuseNavigate,你就掌握了 React 应用的"导航系统"。希望这篇文章能帮你写出更健壮、体验更流畅的路由逻辑!

相关推荐
GreenTea2 小时前
GrokBot 核心成员 Lauren Tan:每月交付 2000 个 PR 的人,是怎么用 AI 的
前端·后端·架构
mCell3 小时前
用 Knip 清理 AI Coding 留下的冗余代码
前端·ai编程·前端工程化
笃行3504 小时前
使用Rokid AIUI做了一个童年推箱子游戏
前端
excel5 小时前
前端加密的作用与使用场景
前端
计算机魔术师6 小时前
纽约时报版权诉讼披露:微软高管内部称训练 AI 是人类历史上最大规模劳动窃取
前端
去伪存真7 小时前
大模型的参数量为什么那么大?
前端·人工智能
IT_陈寒7 小时前
Java空指针这次真把我坑惨了
前端·人工智能·后端
高级程序源8 小时前
django大学生创新创业项目管理系统94923-计算机课程设计、毕业设计
javascript·vue.js·spring boot·后端·python·django·课程设计
八荒启·交互动画8 小时前
# Web特效020—让 Web 特效真正动起来:时间循环应该怎么接
前端·webgl·网页特效·八荒启-交互动画·八荒启
动恰客流统计8 小时前
线下零售数字化浪潮下,客流统计的3个核心发展趋势
大数据·前端·人工智能