本文详细介绍React Router 中 调用 action 的三种核心方式。 它们之间的主要区别在于如何触发 以及调用后是否会引起页面导航。
我们来逐一详细解析,并总结它们的适用场景。
核心概念:什么是"导航 (Navigation)"?
在 React Router 中,一个"导航"并不仅仅是 URL 的变化。它是一个完整的路由过渡流程,通常包括:
- URL 更新:浏览器的地址栏会更新。
- 历史记录:在浏览器的历史记录中添加一个新条目,用户可以点击"后退"按钮返回。
- 数据重新加载 :触发新页面的
loader
函数,获取页面所需数据。 - 组件重新渲染:渲染目标路由的组件。
理解了这一点,我们再来看这三种方法就清晰多了。
方法一:使用 <Form>
组件(声明式)
这是最常见、最直接的调用 action
的方式。
jsx
import { Form } from "react-router";
function SomeComponent() {
return (
// A. 声明一个表单,指向特定 action
<Form action="/projects/123" method="post">
<input type="text" name="title" />
<button type="submit">Submit</button>
</Form>
);
}
代码解析:
A. <Form action="/projects/123" method="post">
:
action="/projects/123"
: 这个action
prop 明确告诉 React Router,当表单提交时,应该去调用/projects/123
这个路由路径下定义的action
函数。method="post"
: 这是关键。它告诉 React Router 这是一个数据提交操作,应该调用action
而不是loader
。- 行为 : 当用户点击 "Submit" 按钮时,React Router 会阻止浏览器默认的全页面刷新,然后发起一次导航 到
/projects/123
。
结果 :会触发一次完整的导航。
适用场景:
- 创建新资源后跳转:比如创建一个新的博客文章,成功后你希望用户直接跳转到新文章的详情页。
- 用户登录/注册:成功后跳转到仪表盘或主页。
- 任何你希望在数据提交后,页面发生完整跳转的场景。
方法二:使用 useSubmit
Hook(命令式)
这种方式让你可以在任何 JavaScript 逻辑中手动触发 一个 action
,而不是必须通过用户点击表单的提交按钮。
jsx
import { useCallback } from "react";
import { useSubmit } from "react-router";
function useQuizTimer() {
// A. 从 hook 中获取 submit 函数
let submit = useSubmit();
// B. 定义一个回调函数,在特定时机调用 submit
let cb = useCallback(() => {
// C. 手动提交数据和指定 action
submit(
{ quizTimedOut: true }, // 要提交的数据
{ action: "/end-quiz", method: "post" } // 目标 action 和方法
);
}, [submit]);
// D. 触发器:一个 10 分钟的计时器
let tenMinutes = 10 * 60 * 1000;
useFakeTimer(tenMinutes, cb);
}
代码解析:
A. let submit = useSubmit()
: 获取一个可以手动触发提交的函数。
B. useCallback(...)
: 使用 useCallback
包装回调函数是一个好习惯,可以避免不必要的重渲染。
C. submit(data, options)
: 这是核心。
- 第一个参数
{ quizTimedOut: true }
是你要提交的数据,React Router 会自动处理它。 - 第二个参数
{ action: "/end-quiz", method: "post" }
明确指定了目标action
的路径和方法。
D. 触发器 : 这里的例子是一个计时器,但它可以是任何事件,比如 WebSocket 消息、键盘快捷键(Ctrl+S
保存)等。
结果 :和 <Form>
一样,会触发一次完整的导航。它只是触发方式不同。
适用场景:
- 自动保存:当用户停止输入一段时间后自动保存草稿。
- 非标准 UI 交互 :比如拖拽一个项目到"完成"区域时,触发更新状态的
action
。 - 定时任务:如示例中的在线测验时间到了,自动提交试卷。
方法三:使用 useFetcher
Hook(无导航交互)
这是最强大的方式,它允许你在不引起页面导航 的情况下,与 action
或 loader
进行交互。
jsx
import { useFetcher } from "react-router";
function Task() {
// A. 获取一个 fetcher 实例
let fetcher = useFetcher();
// B. 通过 fetcher 的状态来更新 UI
let busy = fetcher.state !== "idle";
return (
// C. 使用 fetcher 专属的 Form 组件
<fetcher.Form method="post" action="/update-task/123">
<input type="text" name="title" />
<button type="submit" disabled={busy}>
{busy ? "Saving..." : "Save"}
</button>
</fetcher.Form>
);
}
// D. fetcher 同样支持命令式提交
fetcher.submit(
{ title: "New Title" },
{ action: "/update-task/123", method: "post" }
);
代码解析:
A. let fetcher = useFetcher()
: fetcher
是一个独立的对象,它有自己的状态(state
)、数据(data
)和 Form
组件。
B. fetcher.state
: fetcher
有自己的生命周期状态 ("idle"
, "submitting"
, "loading"
)。你可以用它来创建精细的 UI 反馈,比如禁用按钮或显示加载指示器,而这不会影响全局导航状态 。
C. <fetcher.Form>
: 这个表单的行为和普通 <Form>
不同。当它提交时,它只会把请求发送给 action
,然后更新 fetcher
自己的状态和数据,但不会触发导航 。
D. fetcher.submit(...)
: 和 useSubmit
类似,提供了命令式的调用方式,同样不会触发导航。
结果 :不会触发导航 。URL 不会变,浏览器历史记录不会增加,当前页面的 loader
不会重新运行。
适用场景(非常广泛):
- "点赞"或"收藏" :在一个文章列表页,你希望用户可以点赞,但不想因此刷新整个页面。
- 添加购物车:在商品详情页点击"加入购物车",只更新购物车图标上的数字,页面其他部分保持不变。
- 更新列表中的单项:在一个 To-Do List 中,勾选完成一项任务。
- 任何你希望在"后台"完成数据交互,只在局部更新 UI 的场景。
总结与对比
特性 | <Form> |
useSubmit |
useFetcher |
---|---|---|---|
调用方式 | 声明式 (组件) | 命令式 (Hook) | 声明式 (<fetcher.Form> ) 和命令式 (fetcher.submit ) |
是否引起导航 | 是 | 是 | 否 |
更新浏览器历史 | 是 | 是 | 否 |
触发全局加载 | 是 | 是 | 否 (只影响自身状态) |
核心用途 | 页面级的数据提交和跳转 | 程序化、事件驱动的页面跳转 | 无需跳转的后台数据交互、局部 UI 更新 |
通过这三种方式的组合,React Router 为开发者提供了从简单的页面跳转到复杂的局部更新的全方位数据交互解决方案。