react-router Navigate 组件详解:声明式导航重定向的 API、实现原理与实战用法
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
useNavigateto use in aReact.Componentclass where hooks cannot be used.It's recommended to avoid using this component in favor of
useNavigate.
翻译成实战语言:在函数组件中优先使用 useNavigate;只有两种场景才需要 Navigate:
- 你维护的是 Class 组件,无法调用 Hook;
- 你需要"渲染到某个页面就立刻离开"的声明式重定向——例如访问
/home时自动跳到/about,而不必写useEffect(() => navigate(...), [])。
最简用法(与文档一致):
<Navigate to="/tasks" />
它被从 react-router 包的主入口导出(见 packages/react-router/index.ts 中对 Navigate 与 NavigateProps 的 export)。
二、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>(含 MemoryRouter、BrowserRouter、RouterProvider 等)子树内,否则直接抛出 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(即解析后的目标)与全部导航选项——to、replace、state 任一变化都会重新触发导航。
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 段"。源码注释将其称为to与href的关键区别:// 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,匹配的路由树为 admin → reports/: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 还会做几件容易踩坑的事,源码里都有明确处理:
- 对象形式的
pathname不允许包含?或#(查询串必须放search字段),search里不允许出现#,违反时抛 invariant 错误; - 空字符串
to=""会被当作/(isEmptyPath分支); - 尾斜杠保留:若
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 段为单位做回溯"的体现,也印证了 getResolveToMatches(utils.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,重定向逻辑应延迟到客户端交互或状态更新之后触发。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00