首页
/ react-router Navigate 组件详解:声明式导航重定向的 API、实现原理与实战用法

react-router Navigate 组件详解:声明式导航重定向的 API、实现原理与实战用法

2026-09-06 13:31:40作者:翟萌耘Ralph

Navigate 是 react-router 提供的"组件化导航"能力,等价于 useNavigate Hook 的组件版本,专为无法使用 Hook 的场景(如 Class 组件)以及"渲染即重定向"的场景(登录守卫、路由迁移)设计。本文以官方 API 文档 docs/api/components/Navigate.md 为骨架,结合 packages/react-router 的真实源码,完整覆盖其 4 个 Props、调用签名、相对路由解析机制、StrictMode 安全设计以及外部链接拦截策略,帮你掌握何时用、怎么用、底层怎么跑。

一、定位:useNavigate 的组件化版本

官方文档对 Navigate 的 Summary 定义如下:

A component-based version of useNavigate to use in a React.Component class where hooks cannot be used.

It's recommended to avoid using this component in favor of useNavigate.

翻译成实战语言:在函数组件中优先使用 useNavigate;只有两种场景才需要 Navigate

  1. 你维护的是 Class 组件,无法调用 Hook;
  2. 你需要"渲染到某个页面就立刻离开"的声明式重定向——例如访问 /home 时自动跳到 /about,而不必写 useEffect(() => navigate(...), [])

最简用法(与文档一致):

<Navigate to="/tasks" />

它被从 react-router 包的主入口导出(见 packages/react-router/index.ts 中对 NavigateNavigatePropsexport)。

二、API 签名与 Props 全量说明

文档给出的函数签名为:

function Navigate({ to, replace, state, relative }: NavigateProps): null

注意返回类型是 null——<Navigate /> 本身不渲染任何 DOM,它只负责触发一次导航。这与源码完全一致:packages/react-router/lib/components.tsx 中函数体的最后一行就是 return null

Props 类型定义在同文件的 NavigateProps 接口,共 4 个字段,逐一说明:

Prop 类型 必填 说明
to To(字符串或 Path 对象) 要导航的目标路径。可以是 "/tasks" 这样的字符串,也可以是 { pathname, search, hash } 结构的 Path 对象
replace boolean 是否替换 History 栈中的当前条目(history.replaceState)。置 true 后用户按"后退"不会回到重定向前那个页面
state any 附加到目标 Location 上的状态,存入 history.state,可通过目标页的 useLocation().state 读取
relative RelativeRoutingType"route" | "path" 解释 to 中相对路径的基准,见下文第四节

to 支持对象形式的完整示例:

// 等价于 navigate 的对象参数写法
<Navigate to={{ pathname: "/tasks", search: "?q=high", hash: "#top" }} />

一个典型的"登录守卫 + 传递 state"实战组合:

import { Navigate, useLocation } from "react-router";

function RequireAuth({ children }) {
  let location = useLocation();
  // 未登录时重定向到登录页,且保留"来自哪里"的信息
  return (
    <Navigate
      to="/login"
      replace
      state={{ from: location }}
    />
  );
}

三、实现原理:一次"effect 驱动 + 路径先行解析"的导航

Navigate 源码 可以看到它的执行流程,分五步:

1. 上下文校验

invariant(
  useInRouterContext(),
  `<Navigate> may be used only in the context of a <Router> component.`,
);

<Navigate /> 必须位于某个 <Router>(含 MemoryRouterBrowserRouterRouterProvider 等)子树内,否则直接抛出 invariant 错误。源码注释指出,最常见的诱因是项目里加载了两个版本的 react-router 导致上下文丢失。

2. StaticRouter 下的告警

warning(
  !isStatic,
  `<Navigate> must not be used on the initial render in a <StaticRouter>. ` +
    `This is a no-op, but you should modify your code so the <Navigate> is ` +
    `only ever rendered in response to some user interaction or state change.`,
);

在 SSR/静态渲染(<StaticRouter>)的首次渲染中使用 <Navigate>无效操作(no-op)——服务端无法执行浏览器导航。它不会报错但会打 warning,提示你应当把 <Navigate> 的渲染放到"用户交互或状态变化之后"。

3. 渲染阶段解析路径(StrictMode 关键设计)

let { matches } = React.useContext(RouteContext);
let { pathname: locationPathname } = useLocation();
let navigate = useNavigate();

// Resolve the path outside of the effect so that when effects run twice in
// StrictMode they navigate to the same place
let path = resolveTo(
  to,
  getResolveToMatches(matches),
  locationPathname,
  relative === "path",
);

路径解析被刻意放在渲染期而非 effect 内,源码注释解释了动机:React 18 StrictMode 会双跑 effect,若目标路径依赖"当前渲染时的 location",两次运行可能解析到不同结果;在渲染期先算好 path 并用 JSON.stringify 冻结(jsonPath),保证 effect 重放时目标一致。

4. 外部链接拦截

validateNavigationTarget(
  typeof to === "string" ? to : createPath(to),
  navigator.createHref(path),
  getNavigatorCurrentUrl(navigator),
  "reject",
);

该函数实现在 packages/react-router/lib/router/navigation.ts:将 to 与当前 URL 分别解析后做同源比对,一旦 to 是跨域地址(originalIsExternal || resolvedIsExternal),在 "reject" 策略下直接抛出 External navigation is not allowed 错误。这意味着 <Navigate to="https://other-site.com"> 会在开发期立刻炸掉,而不是静默失效——这是路由库防止误用的一道安全闸。

5. effect 中真正执行导航

React.useEffect(() => {
  navigate(JSON.parse(jsonPath), { replace, state, relative });
}, [navigate, jsonPath, relative, replace, state]);

最终调用的是 useNavigate 返回的 navigate 函数,且依赖数组包含 jsonPath(即解析后的目标)与全部导航选项——toreplacestate 任一变化都会重新触发导航。

useNavigate 本身还有一个细节值得了解:它在 hooks.tsx 中按路由模式分支——

let { isDataRoute } = React.useContext(RouteContext);
return isDataRoute ? useNavigateStable() : useNavigateUnstable();

Declarative 模式(<Routes>/<MemoryRouter> 等)下每次渲染可能得到新的函数引用,而 Data/Framework 模式(createBrowserRouter + <RouterProvider>)下返回稳定引用,且导航会返回一个导航完成时 resolve 的 Promise(返回类型 void | Promise<void>)。因此 <Navigate> 在两类路由体系中都能工作,行为一致。

四、relative:"route""path" 的语义差异

relative 决定 to 中相对路径(尤其是以 .. 开头的片段)如何解析。解析逻辑在 packages/react-router/lib/router/utils.ts 的 resolveTo,核心规则:

  • 默认(等价 relative="route"to 相对于当前路由的路径解析。每个前导 .. 表示"向上跳一级路由",而不是"向上跳一级 URL 段"。源码注释将其称为 tohref 的关键区别:

    // With relative="route" (the default), each leading .. segment means
    // "go up one route" instead of "go up one URL segment".
    
  • relative="path"to 相对于当前 URL 路径名解析,行为更接近浏览器对相对链接的处理。源码中通过 isPathRelative = true 跳过 .. 的路由级回溯(if (!isPathRelative && toPathname.startsWith("..")))。

一个直观例子:当前 URL 为 /admin/reports/2026,匹配的路由树为 adminreports/:year

写法 relative="route"(默认) relative="path"
to="edit" /admin/reports/edit /admin/reports/2026/edit
to=".." /admin/reports(回到 admin 路由下) /admin/reports/2026/.. 归一为 /admin/reports/
to="../settings" /admin/settings /admin/reports/settings

resolveTo 还会做几件容易踩坑的事,源码里都有明确处理:

  1. 对象形式的 pathname 不允许包含 ?#(查询串必须放 search 字段),search 里不允许出现 #,违反时抛 invariant 错误;
  2. 空字符串 to="" 会被当作 /isEmptyPath 分支);
  3. 尾斜杠保留:若 to 显式带尾斜杠,或跳转目标就是当前带尾斜杠的路径,解析结果会补回尾斜杠,避免重定向后 URL 形态突变。

五、典型实战场景

1. 旧路由迁移重定向

<Route path="tasks" element={<Navigate to="/app/tasks" replace />} />
<Route path="app/tasks" element={<TaskList />} />

replace 的目的是不让迁移前的 URL 留在 History 栈里,用户点"后退"不会又跳回来。

2. 登录后回跳

配合上文"定位"一节的 RequireAuth:登录页用 useLocation().state?.from 取出原目标,登录成功后 navigate(from, { replace: true }),形成闭环。

3. 条件重定向

export function ProfileRedirect() {
  let { user } = useAuth();
  // 有角色则去角色对应页面,否则去首页
  return <Navigate to={user?.role ? `/${user.role}` : "/"} replace />;
}

4. 与 <Link> 的分工

<Navigate> 是"渲染即离开"的命令式重定向<Link>/<NavLink>导航入口(用户点击后离开)。需要用户主动触发的跳转不要写成 <Navigate>,否则页面一闪而过且没有可访问的链接语义。

六、测试佐证:绝对、相对与 index 路由的解析行为

官方测试套件 packages/react-router/tests/navigate-test.tsx 系统性地验证了上文的解析规则,例如:

// 绝对路径:/home -> /about
<Route path="home" element={<Navigate to="/about" />} />

// 相对路径(relative="route"):/home -> ../about
<Route path="home" element={<Navigate to="../about" />} />

以及 index 路由中的向上跳转describe("handles upward navigation from an index routes"),见 navigate-test.tsx#L63-L80):

<Route path="home">
  <Route index element={<Navigate to="../about" />} />
</Route>

index 路由不贡献路径段,.. 仍能从 /home 正确解析到 /about——这正是"以路由为单位而非 URL 段为单位做回溯"的体现,也印证了 getResolveToMatchesutils.ts#L1956-L1966)只对"贡献路径的匹配"做解析基准的设计。

七、小结与使用建议

  • Navigate 的 4 个 Props(to / replace / state / relative)与 useNavigate(to, { replace, state, relative }) 一一对应,组件版本只是把这次调用搬进了渲染语义里;
  • 导航在 effect 中执行、路径在渲染期冻结解析,保证了 StrictMode 双跑与 SSR 首渲下的确定性;
  • 外部链接会在 validateNavigationTarget"reject" 策略下直接抛错,跨域跳转必须走 <a> 标签而非 <Navigate>
  • 官方立场明确:能用 useNavigate 就用 Hook<Navigate> 留给 Class 组件与"渲染即重定向"的场景;
  • <StaticRouter> 首次渲染中使用它是 no-op,重定向逻辑应延迟到客户端交互或状态更新之后触发。

主要参考路径:API 文档组件实现路径解析外部导航校验useNavigate Hook行为测试

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