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

相关推荐
ohMyGod_1231 小时前
React16,17,18,19新特性更新对比
前端·javascript·react.js
前端小趴菜051 小时前
React-forwardRef-useImperativeHandle
前端·vue.js·react.js
@大迁世界1 小时前
第1章 React组件开发基础
前端·javascript·react.js·前端框架·ecmascript
Hilaku1 小时前
从一个实战项目,看懂 `new DataTransfer()` 的三大妙用
前端·javascript·jquery
爱分享的程序员1 小时前
前端面试专栏-算法篇:20. 贪心算法与动态规划入门
前端·javascript·node.js
我想说一句1 小时前
事件委托与合成事件:前端性能优化的"偷懒"艺术
前端·javascript
爱泡脚的鸡腿1 小时前
Web第二次笔记
前端·javascript
良辰未晚1 小时前
Canvas 绘制模糊?那是你没搞懂 DPR!
前端·canvas
Dream耀1 小时前
React合成事件揭秘:高效事件处理的幕后机制
前端·javascript
P7Dreamer1 小时前
Vue 3 + Element Plus 实现可定制的动态表格列配置组件
前端·vue.js