首页
/ React Router 中 useNavigationType 全解析:如何判定 POP / PUSH / REPLACE 导航类型

React Router 中 useNavigationType 全解析:如何判定 POP / PUSH / REPLACE 导航类型

2026-09-07 12:22:18作者:苗圣禹Peter

useNavigationType 是 React Router 提供的一个轻量 Hooks,它告诉你"路由器是如何抵达当前 Location 的"——是通过前进/后退(POP)、追加新历史记录(PUSH),还是原地替换历史记录(REPLACE)。本文以官方文档 useNavigationType.md 为主体,结合 react-router 包内的实现源码与测试用例,深入讲解三种导航类型的语义、底层数据流以及真实业务场景下的用法,帮助你用它做页面埋点、转场动画与浏览器历史行为判断。

适用范围(与官方文档标注一致):本 Hooks 在 framework(Vite 插件 / 框架模式)、data(数据路由 RouterProvider)、declarative(<BrowserRouter> 等声明式路由) 三种模式下均可使用。

快速上手:签名与返回值

useNavigationType 不需要任何参数,直接调用即可:

import { useNavigationType } from "react-router";

function NavigationStatus() {
  const navigationType = useNavigationType();
  return <span>进入当前页面方式:{navigationType}</span>;
}

函数签名(见 hooks.tsx 的 JSDoc 与实现):

function useNavigationType(): NavigationType

返回值为 NavigationType 枚举,取值只有三种字符串:"POP""PUSH""REPLACE"

NavigationType 的三种取值:读官方注释不如读源码注释

NavigationType 在源码中定义于 history.ts,本质上就是内部 Action 枚举的重命名导出。它的语义注释非常清晰,原文含义如下:

取值 含义 典型触发场景
POP 历史栈的当前索引被任意改变,即后退/前进。它不描述导航方向,只表示"当前索引变了"。新创建的 history 对象默认就是 POP 浏览器后退/前进按钮、navigate(-1) / navigate(1)、页面首次加载/首次渲染
PUSH 向历史栈新增一条记录,例如点击链接加载新页面;追加记录时,该记录之后的所有历史项都会丢失 点击 <Link> 跳转到不同页面、navigate(to) 默认行为
REPLACE 用新记录替换历史栈当前索引处的记录 <Link replace>navigate(to, { replace: true })、表单提交到当前 URL 的默认行为

阅读源码中的注释还可以得到两个容易被忽略的细节:

  1. POP 不等于"后退"Pop 只说明索引发生变化,不描述方向。Action.Pop 的源码注释明确写道 "It does not describe the direction of the navigation, only that the current index changed."
  2. PUSH 会截断后续历史:源码注释强调 "all subsequent entries in the stack are lost",这是使用 replace 优化历史栈的重要依据。

底层原理:navigationType 是怎样流动到组件的

useNavigationType 的实现只有一行(hooks.tsx),核心是从 React Context 读取:

export function useNavigationType(): NavigationType {
  return React.useContext(LocationContext).navigationType;
}

这里的 LocationContext 定义在 context.ts,其 Provider 的 value 形状为:

interface LocationContextObject {
  location: Location;
  navigationType: NavigationType;
}

也就是说,任何 Router 只要把"当前 location + 抵达它的导航类型"放入 LocationContextuseNavigationType 就能拿到结果。不同 Router 供给该值的方式略有差异:

  • 声明式路由(<BrowserRouter> / <MemoryRouter> 等):底层都收敛到 <Router> 组件。Router 接收一个可选的 navigationType prop(components.tsx),其默认值为 NavigationType.Popcomponents.tsx),随后在渲染时通过 LocationContext.Provider 注入 location 与 navigationType。浏览器 history 每次触发 pop/push/replace/go 事件时,listener 会把对应 action 传入并驱动重新渲染。
  • 数据路由(RouterProvider + createBrowserRouter:导航类型保存在路由状态中。在 router.ts 中可看到路由状态携带 historyAction: NavigationType 字段,初始化为 NavigationType.Pop,PUSH/REPLACE 导航会在提交时写入对应的 action;RouterProvider 通过订阅(dom/lib.tsx 附近的 (newState: { action: NavigationType; location: Location }) 回调)感知 history 变化,最终把 historyAction 暴露为 Context 中的 navigationType
  • 服务端渲染(SSR)StaticRouter 没有真正的浏览器 history,因此直接把 action 硬编码为 NavigationType.Popdom/server.tsx)。也就是说,SSR 输出的 HTML 中 useNavigationType() 恒为 "POP",这是符合预期的——服务器上只有一次"初始抵达"。

顺带一提:useNavigationTypeuseLocation 读取的是同一个 Context,因此它必须运行在某个 Router 内部。若在 Router 之外调用,LocationContext 的默认值是 null!,读取 navigationType 会直接抛错。

实战用法:什么时候才需要关心导航类型

1. 页面浏览埋点(Analytics)

经典场景是统计 PV。useLocation() 可以感知 location 变化,但无法区分是刷新(POP)还是站内跳转(PUSH);配合 useNavigationType 即可准确判断"一次全新到达"还是"同一次页面会话中的导航"。例如希望"前进/后退回到的页面不再重复计数"时,可在 effect 中只对 PUSH 计数。若把导航类型作为副作用触发依据,效果与直接依赖 location 等价,但业务分支上能拿到类型信息:

import { useEffect } from "react";
import { useLocation, useNavigationType } from "react-router";

function PageViewTracker() {
  const location = useLocation();
  const navigationType = useNavigationType();

  useEffect(() => {
    if (navigationType !== "POP") {
      trackPageView(location.pathname);
    }
  }, [location, navigationType]);

  return null;
}

2. 按前进 / 后退区分转场方向

配合 CSS 过渡或动画库时,经常需要知道"用户是从左边滑入(进入子页面)还是从右边退回(返回上一页)"。POP + useLocation 的 key 对比是判断"后退"的常见手段。虽然 useNavigationType 不区分后退的方向(POP 只表示索引变化),但在单层后退判断中,配合 useLocation().key 的先后关系即可补足方向信息,从而让列表页与详情页使用相反的转场动画。

3. 依赖 <Link> / <Form> 行为的联动逻辑

理解了"何时是 PUSH、何时是 REPLACE"后,很多看似怪异的 UI 状态(例如返回键失效、面包屑多跳了一层)都能对号入座:

  • 点击 <Link> 跳转(不同 pathname / search / hash)默认 PUSH
  • 点击指向当前页的 <Link to="."> 默认会被优化为 REPLACE(避免产生无意义的重复历史项);
  • 表单提交到当前 URL 默认 REPLACE(见 router.ts 中关于 submission 默认行为的注释),以保持"回退即回到表单前状态"的直觉;
  • loader/action 抛出 redirect 响应时,若重定向发生在导航过程中,通常按 REPLACE 处理,避免把"已被重定向掉的中间地址"留在历史栈里(见 router.ts 对 redirect 导航类型的判断)。

4. 依赖组件展示"如何到达本页"

例如调试工具栏、面包屑提示、或需要区分"直接输入 URL 进入(POP)"与"站内跳转进入(PUSH)"的运营活动页逻辑。用返回值直接渲染即可,无需额外传参。

完整示例:与测试用例一一对应

仓库中的 link-push-test.tsx 是验证 useNavigationType 行为最直观的测试:它在页面组件内渲染 <p>{useNavigationType()}</p>,用 MemoryRouter + react-test-renderer 模拟点击 <Link>,并断言渲染结果。我们据此可整理出以下可直接运行的行为对照示例:

import { MemoryRouter, Routes, Route, Link, useNavigationType } from "react-router";

function NavigationTypeBadge() {
  return <p>{useNavigationType()}</p>;
}

export default function App() {
  return (
    <MemoryRouter initialEntries={["/home"]}>
      <Routes>
        <Route path="home" element={<Home />} />
        <Route path="about" element={<About />} />
      </Routes>
    </MemoryRouter>
  );
}

function Home() {
  return (
    <div>
      {/* 点击后导航到 /about:PUSH */}
      <Link to="../about">About</Link>
      {/* 指向本页:REPLACE */}
      <Link to=".">Home(同页替换)</Link>
      {/* 强制 PUSH */}
      <Link to="." replace={false}>
        Home(强制追加)
      </Link>
    </div>
  );
}

function About() {
  return <NavigationTypeBadge />;
}

结合 link-push-test.tsx 中的测试结论,行为归纳如下:

触发动作 期望导航类型 测试位置
点击 <Link to="../about">(不同路径) PUSH link-push-test.tsx "performs a push"
点击 <Link to="?name=michael">(仅 query 变化) PUSH "performs a push with the existing pathname"
点击 <Link to="#bio">(仅 hash 变化) PUSH "performs a push with the existing pathname"
点击指向本页的 <Link to=".">(默认) REPLACE "performs a replace"
点击 <Link to="." replace={false}> PUSH "performs a push"
点击同源同 basename 的绝对 URL PUSH "performs a push"

这段测试同时印证了 <Link>replace 默认语义:指向"当前所在路由"时 Link 会自动降级为 REPLACE,需要强制追加历史记录时显式传 replace={false}。若使用 useNavigate 编程式导航,则通过 navigate(to, { replace: true }) 显式控制;<Navigate replace> 组件同理。

与其他 API 的关系

  • useLocation 搭配:一个回答"现在在哪",一个回答"怎么到的",二者读取同一个 LocationContext,因此重渲染时机完全一致,不会出现数据不同步。
  • useHref / useResolvedPath 无关:它们是计算目标地址的,不关心导航类型;需要注意区分。
  • useLinkClickHandler:点击 <Link> 后最终执行的 push/replace 决策链路,可以在该 handler 与 <Link>replace prop 逻辑中追溯(相关组件文档见 Link.mdNavigate.md)。
  • 更统一的替代方案 unstable_useRouterState:在 framework / data 模式下,新提供的 unstable_useRouterStateuseLocationuseParamsuseMatchesuseNavigationuseNavigationType 合并为一个 Hook(hooks.tsx)。其中 active.type 字段即是 useNavigationType() 的等价物,active 反映已提交的当前 location,pending 反映进行中的导航;若你的代码库已大规模使用该统一 Hook,可以不再单独调用 useNavigationType

小结与常见误区

  • useNavigationType() 永远只返回 "POP" / "PUSH" / "REPLACE" 三选一,首次渲染(含初始加载与刷新)基本是 POP——因为它描述的是"历史索引如何变化",而应用启动并不新增/替换历史项。
  • POP 不代表"后退"方向,只代表"索引动了";区分前进还是后退需要结合 useLocation().key 做前后对比。
  • 数据路由下表单重复提交、redirect 响应默认倾向 REPLACE,这是出于"回退体验"的设计选择,不是 bug。
  • SSR 阶段(StaticRouter)该 Hook 恒为 "POP",需要客户端水合后再判断真实导航来源。

官方 API 参考入口可继续阅读 hooks/index.md 与其他导航类 Hooks(如 useNavigate.mduseLocation.md),从声明式路由与数据路由的切换方式可参见 declarative-routers/index.mddata-routers/index.md

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.74 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.81 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
595
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
920
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.63 K
1.02 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
518
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
389