react-router-navigation-summary

7 min read
Table of Contents

核心导航方式

React Router 提供了两种主要的导航方式:声明式 (Declarative)编程式 (Imperative)

最常用的方式,用于创建应用内的链接。

  • <Link>: 基础链接组件,相当于 HTML 的 <a> 标签,但不会触发页面完全刷新。
  • <NavLink>: 特殊的 <Link>,知道自己是否处于“激活”状态。
tsx
import { Link, NavLink } from "react-router";
// 基础用法
<Link to="/home">Go Home</Link>
// NavLink 激活状态样式
<NavLink
to="/messages"
className={({ isActive, isPending }) =>
isPending ? "pending" : isActive ? "active" : ""
}
>
Messages
</NavLink>

2. 编程式导航 (useNavigate)

当需要在执行某些逻辑(如提交表单、登录成功)后跳转时使用。

tsx
import { useNavigate } from "react-router";
function LoginForm() {
const navigate = useNavigate();
async function handleSubmit(event) {
event.preventDefault();
await login();
// 跳转到主页,replace: true 表示替换当前历史记录,用户无法回退到登录页
navigate("/dashboard", { replace: true });
// 或者后退一步
// navigate(-1);
}
return <form onSubmit={handleSubmit}>...</form>;
}

3. 组件式重定向 (<Navigate>)

用于在渲染时立即重定向。常见于保护路由(Protected Routes)。

tsx
import { Navigate } from "react-router";
const ProtectedRoute = ({ user, children }) => {
if (!user) {
// 没登录直接跳去登录页,replace 同样是为了防止回退死循环
return <Navigate to="/login" replace />;
}
return children;
};

4. Loader/Action 中的重定向 (redirect)

在 v6.4+ (Data Router) 中,推荐在 loaderaction 中处理重定向,而不是在组件渲染时。

tsx
import { redirect } from "react-router";
export const loader = async () => {
const user = await getUser();
if (!user) {
throw redirect("/login");
}
return { user };
};

避坑指南 (Common Pitfalls)

1. 相对路径 vs 绝对路径

  • 绝对路径: 以 / 开头(如 /about),总是从根路径开始匹配。
  • 相对路径: 不以 / 开头(如 details),基于当前路由路径进行拼接。

坑点: 如果你在 /users/123 页面,点击 <Link to="edit">,会跳转到 /users/123/edit。但如果你写成了 <Link to="/edit">,则会跳到根目录下的 /edit。 另外,.. 在相对路径中的行为是指向路由层级的父级,而不是 URL 路径的父级(虽然通常一致,但在嵌套路由中可能有区别)。

2. replace: true 的重要性

场景: 登录页 -> 仪表盘。 如果不加 replace: true,用户点击浏览器“后退”按钮会回到登录页(此时用户已登录,可能又被重定向回仪表盘,体验很差)。 解决: 状态改变导致的跳转(Login -> Home, Form Success -> List),通常都应该使用 { replace: true }

3. useEffect 中使用 useNavigate 的竞态/双重调用

坑点: 在 useEffect 中调用 navigate 可能会在 React Strict Mode (开发环境) 下执行两次,或者与其他状态更新产生冲突。 建议:

  • 优先使用 loader 中的 redirect 处理前置跳转逻辑。
  • 如果是交互后的跳转,放在事件处理函数中,而不是 useEffect
  • 如果必须在 useEffect 中跳转,确保依赖项正确,并且该逻辑是幂等的。

4. 路由状态 (state) 的易失性

你可以通过 state 传递数据:

tsx
navigate("/target", { state: { from: "home", id: 123 } });

在目标页面获取:

tsx
const location = useLocation();
const { from, id } = location.state || {}; // 必须处理 null/undefined

坑点: 用户刷新页面或者直接复制链接在新标签页打开时,state 会丢失(变为 null)。 解决: state 仅用于非关键的 UI 增强(如“返回上一页”的路径记录、Toast 消息)。核心业务数据(如 ID)必须放在 URL 参数 (paramssearchParams) 中,以便刷新后能重新获取。

5. 阻塞导航 (useBlocker)

在旧版本中常用的 <Prompt> 已被移除。v6 使用 useBlocker(需要 Data Router 支持)。 坑点: 浏览器对于 beforeunload 事件的限制越来越严,自定义 UI 的阻塞导航只能在应用内路由跳转时生效,无法完全阻止用户关闭浏览器标签页或刷新(只能弹默认的浏览器原生对话框)。

新手误区: 使用 <a> 标签跳转内部页面。 后果: 浏览器会发起全新的页面请求,React 应用会被卸载并重新加载,导致:

  • 状态丢失(Redux/Context 重置)。
  • 性能下降(需要重新下载/解析 JS)。 原则: 内部跳转永远使用 <Link>,外部链接才使用 <a>

总结

  1. 优先声明式: 能用 <Link> 解决的,不要写 onClick={() => navigate(...)}
  2. 拥抱 Data Router: v6.4+ 的 loader/action 模式让重定向逻辑更清晰,避免了组件渲染后的“闪烁”跳转。
  3. URL 是单一事实来源: 不要过度依赖 state 传参,尽量将状态同步到 URL 上,增强页面的可分享性和健壮性。

后记

My avatar

感谢你读到这里。

这座小站更像一份持续维护的“终端笔记”:记录我解决问题的过程,也记录走过的弯路。

如果这篇内容对你有一点点帮助:

  • 点个赞 / 收藏一下,方便你下次回来继续翻
  • 欢迎在评论区补充你的做法(或者指出我的疏漏)
  • 想持续收到更新:可以订阅 RSS(在页面底部)

我们下篇见。


More Posts

评论

评论 (0)

请先登录后再发表评论

加载中...