react-router-action

5 min read
Table of Contents

核心概念

  1. Action:

    • 路由级别的异步函数,用于处理数据提交。
    • 在组件提交表单(Form)后执行。
    • 自动重新验证 (Revalidation): Action 执行成功后,React Router 会自动重新运行页面上所有的 Loader,确保 UI 显示最新的数据。
  2. 命名规则 (重要):

    • SPA 模式 (ssr: false): 必须导出为 clientAction
    • SSR 模式 (ssr: true): 服务端处理导出 action,客户端处理导出 clientAction

基本用法

1. 定义 Action

typescript
import type { ActionFunctionArgs } from "react-router";
import { redirect } from "react-router";
// SPA 模式下使用 clientAction
export async function clientAction({ request, params }: ActionFunctionArgs) {
// 1. 获取表单数据
const formData = await request.formData();
const title = formData.get("title");
// 2. 数据校验
if (typeof title !== "string" || title.length === 0) {
return { error: "标题不能为空" };
}
// 3. 执行副作用 (API 调用)
await createPost({ title });
// 4. 返回结果或重定向
return redirect("/posts");
}

2. 触发 Action

方式 A: <Form> 组件 (全页跳转)

适用于普通的页面级表单提交。

typescript
import { Form, useActionData } from "react-router";
export default function NewPost() {
const actionData = useActionData<typeof clientAction>();
return (
<Form method="post">
<input name="title" />
{actionData?.error && <p>{actionData.error}</p>}
<button type="submit">提交</button>
</Form>
);
}

方式 B: useFetcher (局部更新)

适用于不进行页面跳转的操作(如点赞、加入购物车、弹窗表单)。

typescript
import { useFetcher } from "react-router";
export default function LikeButton() {
const fetcher = useFetcher<typeof clientAction>();
const isSubmitting = fetcher.state === "submitting";
return (
<fetcher.Form method="post" action="/like">
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? "点赞中..." : "点赞"}
</button>
</fetcher.Form>
);
}

方式 C: 编程式提交 (Antd 等第三方库集成)

当使用 Antd FormonFinish 时,手动调用 fetcher.submit

typescript
const fetcher = useFetcher();
const onFinish = (values) => {
// 提交数据到 Action
fetcher.submit(values, { method: "post" });
};
// 获取 Action 返回值
const result = fetcher.data;

常见坑点 (Pitfalls) 🚨

1. 手动调用 Action 函数

  • 错误: const res = await clientAction(...)
  • 后果: 失去了 React Router 的核心优势(加载状态管理、自动重新验证 Loader)。
  • 正确: 必须通过 <Form>fetcher.submit 触发。

2. SPA 模式下的命名

  • 错误: 在 ssr: false 项目中导出 export async function action(...)
  • 后果: 报错 invalid route export 或 Action 不生效。
  • 正确: 必须命名为 clientAction

3. FormData 与 JSON 的混淆

  • 现象: 在 clientActionawait request.json() 报错。
  • 原因: 默认 <Form>fetcher.submit 提交的是 application/x-www-form-urlencodedmultipart/form-data
  • 解决:
    • 标准做法是使用 request.formData()
    • 如果必须发 JSON,需在 submit 时指定 encType:
typescript
fetcher.submit(data, { method: "post", encType: "application/json" });

然后在 Action 中使用 request.json()

4. 第三方 UI 库 (Antd) 的嵌套对象

  • 现象: Antd Form 返回 { user: { name: "xxx" } },直接传给 fetcher.submit 后,在 Action 中 formData.get('user.name') 取不到值。
  • 原因: fetcher.submit 默认会将对象扁平化为 FormData。
  • 解决:
    • 方案一:手动扁平化键名 { "user.name": values.user.name }
    • 方案二:使用 encType: "application/json" 提交 JSON 格式。

5. Action 不会重新渲染当前组件的 Loader 数据?

  • 机制: 默认情况下,Action 成功后会重新验证所有活跃路由的 Loader。
  • 例外: 如果你在 Action 中返回了非 2xx/3xx 状态码(比如 400校验错误),Loader 不会重新运行。
  • 坑点: 如果你想在校验错误时也刷新某些数据,需要注意这一点。

6. fetcher.data vs useActionData

  • useActionData: 获取当前页面 URL 对应的 Action 返回值(通常用于 <Form> 提交)。
  • fetcher.data: 获取特定 fetcher 实例对应的 Action 返回值(用于局部交互)。
  • 混用后果: 在 useFetcher 提交后去读 useActionData 是拿不到结果的。

后记

My avatar

感谢你读到这里。

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

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

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

我们下篇见。


More Posts

评论

评论 (0)

请先登录后再发表评论

加载中...