React Router 中调用 Actions 的三种方式详解

本文详细介绍React Router 中 调用 action 的三种核心方式。 它们之间的主要区别在于如何触发 以及调用后是否会引起页面导航

我们来逐一详细解析,并总结它们的适用场景。

核心概念:什么是"导航 (Navigation)"?

在 React Router 中,一个"导航"并不仅仅是 URL 的变化。它是一个完整的路由过渡流程,通常包括:

  1. URL 更新:浏览器的地址栏会更新。
  2. 历史记录:在浏览器的历史记录中添加一个新条目,用户可以点击"后退"按钮返回。
  3. 数据重新加载 :触发新页面的 loader 函数,获取页面所需数据。
  4. 组件重新渲染:渲染目标路由的组件。

理解了这一点,我们再来看这三种方法就清晰多了。


方法一:使用 <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(无导航交互)

这是最强大的方式,它允许你在不引起页面导航 的情况下,与 actionloader 进行交互。

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 为开发者提供了从简单的页面跳转到复杂的局部更新的全方位数据交互解决方案。

相关推荐
前端工作日常2 小时前
我理解的`npm pack` 和 `npm install <local-path>`
前端
李剑一2 小时前
说个多年老前端都不知道的标签正确玩法——q标签
前端
嘉小华3 小时前
大白话讲解 Android屏幕适配相关概念(dp、px 和 dpi)
前端
姑苏洛言3 小时前
在开发跑腿小程序集成地图时,遇到的坑,MapContext.includePoints(Object object)接口无效在组件中使用无效?
前端
奇舞精选3 小时前
Prompt 工程实用技巧:掌握高效 AI 交互核心
前端·openai
Danny_FD3 小时前
React中可有可无的优化-对象类型的使用
前端·javascript
用户757582318553 小时前
混合应用开发:企业降本增效之道——面向2025年移动应用开发趋势的实践路径
前端
P1erce3 小时前
记一次微信小程序分包经历
前端
LeeAt3 小时前
从Promise到async/await的逻辑演进
前端·javascript
等一个晴天丶3 小时前
不一样的 TypeScript 入门手册
前端