首页
/ React Router 导航指南:NavLink / Link / useNavigate 的选用与实现原理

React Router 导航指南:NavLink / Link / useNavigate 的选用与实现原理

2026-09-07 22:47:05作者:霍妲思

React Router 的声明式(Declarative)模式提供了一套完整、渐进增强的导航 API。本文以 docs/start/declarative/navigating.md 为核心脉络,系统讲解 <NavLink><Link>useNavigate 三种导航方式各自的适用场景、完整用法与底层实现。读完本文,你将能够为应用搭建带激活态的主导航栏、正确插入业务文案中的内链,并在表单提交、登录超时、倒计时跳转等"非用户点击"场景下写出可靠的编程式导航。

前置阅读:若尚未搭建声明式应用并配置路由,请先阅读 安装教程路由配置教程。本文聚焦于三者的关系、激活状态匹配机制、常用属性与源码层面的运行原理,并会结合 NavLink API 文档Link API 文档useNavigate API 文档 进行深化。

三种导航方式如何选择

用户在你的应用中主要通过三个 API 完成导航:

API 形态 适用场景 是否自动携带激活态
<NavLink> 组件 需要渲染"当前处于哪个页面"的激活态,典型如顶部/侧边主导航 ✅ 是
<Link> 组件 不需要激活样式的普通链接,如正文里的"登录"、"了解更多" ❌ 否
useNavigate() Hook 用户没有点击、由程序触发导航的场景 ❌ 否

一句话归纳:需要"高亮当前位置"用 NavLink;只是跳转用 Link;事件/定时器驱动跳转用 useNavigate

NavLink:带激活状态的路由链接

<NavLink> 专为"需要根据当前 URL 渲染激活状态"的导航而生。当链接对应的路由匹配当前地址时,组件会自动为它添加激活态。一个典型的站点头部导航如下(原文示例):

import { NavLink } from "react-router";

export function MyAppNav() {
  return (
    <nav>
      <NavLink to="/" end>
        Home
      </NavLink>
      <NavLink to="/trending" end>
        Trending Concerts
      </NavLink>
      <NavLink to="/concerts">All Concerts</NavLink>
      <NavLink to="/account">Account</NavLink>
    </nav>
  );
}

默认激活态:.active 类与 aria-current

当一个 NavLink 处于激活状态时,它自动携带 .active 类名,因此你只需要几行 CSS 即可完成高亮,无需在组件里手动比对 URL:

a.active {
  color: red;
}

同时,激活的 NavLink 会自动设置 aria-current="page" 无障碍属性,屏幕阅读器可以借此向用户播报"当前页面",这是原生 <a> + 手动比对方案容易遗漏的细节。该行为在 NavLink 源码NavLinkWithRef 实现与 NavLink API 文档 中均有明确说明。

className / style / children 回调:按状态渲染

除默认 .active 类外,classNamestylechildren 三个属性都支持以函数方式传入,函数接收包含当前导航状态的渲染参数对象(isActive 等),实现内联样式与条件渲染:

// className:Tailwind 等原子化 CSS 下更常用
<NavLink
  to="/messages"
  className={({ isActive }) =>
    isActive ? "text-red-500" : "text-black"
  }
>
  Messages
</NavLink>
// style:直接以内联对象方式切换颜色
<NavLink
  to="/messages"
  style={({ isActive }) => ({
    color: isActive ? "red" : "black",
  })}
>
  Messages
</NavLink>
// children:children 作为函数时,可对整个子节点做条件渲染
<NavLink to="/message">
  {({ isActive }) => (
    <span className={isActive ? "active" : ""}>
      {isActive ? "👉" : ""} Tasks
    </span>
  )}
</NavLink>

值得留意的是,函数参数对象在 Framework / Data 模式下还额外包含 isPending(导航加载中)、isTransitioning(View Transition 进行中)等字段。在声明式(Declarative)模式下只有 isActive(以及部分场景的其它字段)可用pending 仅在 Framework 与 Data 模式可用,CSS 侧可分别用 a.pendinga.transitioning 承接。

end:精确控制激活匹配范围

NavLink 的激活判断默认是前缀匹配:只要当前 URL 以链接的 to 路径开头就视为激活。这在"All Concerts"这类包含子路由的链接上是期望行为,但对于父级链接可能造成"所有子页面都被点亮"的困扰。此时用 end 收窄匹配,要求 URL 正好匹配到 to 的结尾

Link URL isActive
<NavLink to="/tasks" /> /tasks true
<NavLink to="/tasks" /> /tasks/123 true
<NavLink to="/tasks" end /> /tasks true
<NavLink to="/tasks" end /> /tasks/123 false

因此上面的导航示例中,HomeTrending Concerts 都加了 end:避免在子页面时首页、趋势页链接仍保持激活。特殊例外<NavLink to="/">:因为任何 URL 都以 / 开头,为了不让它默认匹配每一个路由,React Router 会忽略 end 的实际语义,只在恰好处于根路由时才判定激活。

NavLink 还支持 caseSensitive(激活匹配区分大小写)与从 <Link> 继承的全部属性,完整属性说明见 NavLink.md

Link:不需要激活态时的普通链接

当链接只是"从一个页面到另一个页面",不需要高亮当前项时,使用更轻量的 <Link>

import { Link } from "react-router";

export function LoggedOutMessage() {
  return (
    <p>
      You've been logged out.{" "}
      <Link to="/login">Login again</Link>
    </p>
  );
}

从实现上看,<Link> 是对原生 <a href>渐进增强封装(见 Link.md):它仍渲染为一个真实的 <a> 元素,因此右键打开新标签、中键点击、键盘回车触发、屏幕阅读器朗读等浏览器原生行为全部保留;唯一差别是普通左键点击会被拦截并交给客户端路由处理,实现不刷新页面的平滑跳转,这正是 react-router 源码useLinkClickHandler 的核心职责。如果对点击细节感兴趣,可进一步阅读 Link 组件文档

<Link> 常用的属性还包括:

属性 作用
to 目标地址。可为字符串("/some/path")或 { pathname, search, hash } 部分路径对象
replace 是否替换当前 History 栈记录(默认是 push 新记录)
state 携带持久化的客户端路由状态,目标页通过 useLocation().state 读取;基于 history.state 实现,服务器端不可见
relative 相对路径解析方式:"route"(默认,按路由层级,可跨多个路由段回退)或 "path"(按 URL 段,.. 只回退一级)
reloadDocument 强制走浏览器文档级导航(等同普通 <a>),不做客户端路由
preventScrollReset / viewTransition (Framework/Data 模式)配合 ScrollRestoration 阻止滚动复位、启用 View Transition

其中 to 的对象形态与 replacestate 的语义对声明式、Data、Framework 三种模式都生效:

// to 的对象形态
<Link
  to={{
    pathname: "/some/path",
    search: "?query=string",
    hash: "#hash",
  }}
/>
// replace:假设历史栈为 A -> B,普通点击得到 A -> B -> C,
// 加上 replace 后 B 被替换,得到 A -> C
<Link replace />
// state:目标组件用 useLocation() 读取 location.state
<Link to="/somewhere/else" state={{ some: "value" }} />

NavLink 内部正是包裹了 <Link> 并叠加激活态计算逻辑(实现见 packages/react-router/lib/dom/lib.tsxNavLinkWithRef,它复用了 Link 的 useLinkClickHandler 交互层),所以两者共享的 to/replace/state/relative/reloadDocument 行为完全一致。

useNavigate:程序驱动的编程式导航

何时应该用它(以及何时不该)

useNavigate 允许开发者在用户没有主动点击的情况下把用户带到新页面。但原文给出了一个非常重要的忠告:常规导航尽量用 Link / NavLink,因为它们提供了更优的默认体验——键盘事件支持、无障碍标注、"新窗口打开"、右键菜单等(这些均来自真实 <a> 元素)。只有"不是用户交互、但必须跳转"时才使用 useNavigate,典型场景:

  • 表单提交完成之后(如表单逻辑封装在组件内,提交成功回调中跳转)
  • 用户因长时间无操作被登出(window.setTimeout/空闲检测触发)
  • 倒计时 UI(测验计时结束自动跳到结果页)
import { useNavigate } from "react-router";

export function LoginPage() {
  let navigate = useNavigate();

  return (
    <>
      <MyHeader />
      <MyLoginForm
        onSuccess={() => {
          navigate("/dashboard");
        }}
      />
      <MyFooter />
    </>
  );
}

需要指出:如果导航决策发生在提交数据或加载数据的过程中(即涉及 Data / Framework 模式的 action / loader),React Router 更推荐直接返回 redirect(),而不是在组件里用 useNavigate(见 redirect 工具useNavigate 文档)。

签名与常用调用形式

返回的导航函数签名形如 navigate(to, options?),且支持 navigate(delta) 这种整数形式的"前进/后退":

// 基本跳转,to 可以是字符串或 To 对象
navigate("/some/route");
navigate("/some/route?search=param");

// 前进 / 后退历史栈:
// navigate(-1) 常用于关闭弹层并回到上一页
// navigate(1)  常用于多步骤向导流程
navigate(-1);
navigate(1);

// 替换而非新增历史记录,行为类似服务端 302 重定向
navigate("/some/route", { replace: true });

// 附带 location state,目标页用 useLocation().state 读取
navigate(
  {
    pathname: "/some/route",
    search: "?search=param",
    hash: "#hash",
  },
  {
    state: { some: "state" },
  },
);

其中 options 在各模式下均支持 relative"route" / "path")、replacestateflushSyncpreventScrollResetviewTransition 仅在 Framework / Data 模式可用。

navigate(delta) 要格外谨慎:如果应用允许从任意入口直达某路由,而该路由上又有一个"返回/前进"按钮,那么 History 栈里未必存在可跳转的记录,甚至可能跳到别的域名。只有在确信用户必然在栈中拥有目标记录时才使用数字导航。

不同模式的实现差异(源码级)

packages/react-router/lib/hooks.tsx 中,useNavigate 的导出实现会根据上下文选择两条路径:

export function useNavigate(): NavigateFunction {
  let { isDataRoute } = React.useContext(RouteContext);
  // eslint-disable-next-line react-hooks/rules-of-hooks
  return isDataRoute ? useNavigateStable() : useNavigateUnstable();
}
  • 声明式模式(如 <BrowserRouter> + <Routes>)走 useNavigateUnstable(),不涉及数据路由,纯粹操作本地路由状态;
  • Data / Framework 模式(<RouterProvider> 或框架路由)走 useNavigateStable(),返回的导航函数身份稳定(导航期间引用不变),且返回 Promise<void>,在导航真正完成后 resolve。

这种差异带来一个类型层面的现实问题:useNavigate 的实际返回类型是 void | Promise<void> 的联合类型,使用 typescript-eslint 时可能触发 no-floating-promises 告警,或在 Framework/Data 模式下 React.use(navigate()) 出现类型误报。官方给出的解决方式是按你使用的路由器做模块类型增强

// 若使用 <BrowserRouter>(声明式)
declare module "react-router" {
  interface NavigateFunction {
    (to: To, options?: NavigateOptions): void;
    (delta: number): void;
  }
}

// 若使用 <RouterProvider> 或 Framework 模式
declare module "react-router" {
  interface NavigateFunction {
    (to: To, options?: NavigateOptions): Promise<void>;
    (delta: number): Promise<void>;
  }
}

小结:一个组件优先的决策流程

你的场景 首选 API
主导航、Tab、面包屑等需要高亮当前项 <NavLink>(配合 end 收窄匹配)
正文内链、按钮式跳转、普通页面间切换 <Link>
表单提交成功后、登出倒计时、测验/轮询等程序触发 useNavigate()
loader / action 流程中的数据驱动跳转 redirect() 返回

需要注意的是:真正需要"打开新标签"的站外跳转或下载链接,直接用原生 <a href target="_blank"> 即可,无需引入 React Router 组件。导航是路由系统的门面,<NavLink> 让门面自带激活语义,<Link> 让门面保持原生可用性,useNavigate 让程序在关键时刻"伸手导航"——三者各司其职,构成了完整的 React Router 导航矩阵。接下来可继续学习 URL 值 以掌握 useSearchParams 等处理查询参数的手段。

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

项目优选

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