主分支
分支
主分支 (6.23.1)开发分支
版本
6.23.1v4/5.xv3.x
useFetcher
本页内容

useFetcher

在 HTML/HTTP 中,数据变动和加载是通过导航来模拟的:<a href><form action>。两者都会导致浏览器导航。React Router 的等效项是 <Link><Form>

但有时您想在导航之外调用 loader,或者调用 action(并在页面上获取数据以重新验证)而无需更改 URL。或者您需要同时进行多个变动。

许多与服务器的交互不是导航事件。此钩子允许您将 UI 连接到您的操作和加载器,而无需导航。

此功能仅在使用数据路由器时有效,请参阅 选择路由器

这在您需要以下情况时很有用

  • 获取与 UI 路由无关的数据(弹出窗口、动态表单等)
  • 将数据提交到操作而无需导航(共享组件,例如新闻稿注册)
  • 处理列表中的多个并发提交(典型的“待办事项应用程序”列表,您可以在其中点击多个按钮,并且所有按钮都应同时处于挂起状态)
  • 无限滚动容器
  • 等等!

如果您正在构建高度交互式的“类似应用程序”的用户界面,您将经常使用 useFetcher

import { useFetcher } from "react-router-dom";

function SomeComponent() {
  const fetcher = useFetcher();

  // call submit or load in a useEffect
  React.useEffect(() => {
    fetcher.submit(data, options);
    fetcher.load(href);
  }, [fetcher]);

  // build your UI with these properties
  fetcher.state;
  fetcher.formData;
  fetcher.json;
  fetcher.text;
  fetcher.formMethod;
  fetcher.formAction;
  fetcher.data;

  // render a form that doesn't cause navigation
  return <fetcher.Form />;
}

Fetcher 具有许多内置行为

  • 自动处理获取中断时的取消
  • 使用 POST、PUT、PATCH、DELETE 提交时,将首先调用操作
    • 操作完成后,页面上的数据将重新验证以捕获可能发生的任何变动,从而自动使您的 UI 与您的服务器状态保持同步
  • 当多个 fetcher 同时处于活动状态时,它将
    • 提交每个 fetcher 返回的最新的可用数据
    • 确保没有过时的加载会覆盖更新的数据,无论响应返回的顺序如何
  • 通过渲染最近的 errorElement 来处理未捕获的错误(就像从 <Link><Form> 进行的正常导航一样)
  • 如果您的操作/加载器被调用时返回重定向,则会重定向应用程序(就像从 <Link><Form> 进行的正常导航一样)

选项

key

默认情况下,useFetcher 会生成一个作用域限定于该组件的唯一 fetcher(但是,它可以在 useFetchers() 中被查找,只要它处于活动状态)。如果您想使用自己的 key 来标识 fetcher,以便您可以从应用程序中的其他地方访问它,您可以使用 key 选项来实现

function AddToBagButton() {
  const fetcher = useFetcher({ key: "add-to-bag" });
  return <fetcher.Form method="post">...</fetcher.Form>;
}

// Then, up in the header...
function CartCount({ count }) {
  const fetcher = useFetcher({ key: "add-to-bag" });
  const inFlightCount = Number(
    fetcher.formData?.get("quantity") || 0
  );
  const optimisticCount = count + inFlightCount;
  return (
    <>
      <BagIcon />
      <span>{optimisticCount}</span>
    </>
  );
}

组件

fetcher.Form

<Form> 相同,只是它不会导致导航。 (希望您能克服 JSX 中的点!)

function SomeComponent() {
  const fetcher = useFetcher();
  return (
    <fetcher.Form method="post" action="/some/route">
      <input type="text" />
    </fetcher.Form>
  );
}

方法

fetcher.load(href, options)

从路由加载器加载数据。

import { useFetcher } from "react-router-dom";

function SomeComponent() {
  const fetcher = useFetcher();

  useEffect(() => {
    if (fetcher.state === "idle" && !fetcher.data) {
      fetcher.load("/some/route");
    }
  }, [fetcher]);

  return <div>{fetcher.data || "Loading..."}</div>;
}

虽然一个 URL 可能匹配多个嵌套路由,但 fetcher.load() 调用只会调用叶子匹配的加载器(或 索引路由 的父级)。

如果您发现自己在点击处理程序中调用此函数,您可能可以通过使用 <fetcher.Form> 来简化您的代码。

任何在页面上处于活动状态的 fetcher.load 调用都将在重新验证时重新执行(在导航提交、另一个 fetcher 提交或 useRevalidator() 调用之后)。

options.unstable_flushSync

unstable_flushSync 选项告诉 React Router DOM 将此 fetcher.load 的初始状态更新包装在一个 ReactDOM.flushSync 调用中,而不是默认的 React.startTransition。这允许您在更新刷新到 DOM 后立即执行同步 DOM 操作。

请注意,此 API 被标记为不稳定,可能会在没有重大版本发布的情况下发生重大更改。

fetcher.submit()

<fetcher.Form> 的命令式版本。如果用户交互应该启动获取,您应该使用 <fetcher.Form>。但是,如果您(程序员)正在启动获取(不是响应用户点击按钮等),那么请使用此函数。

例如,您可能希望在一定时间的空闲时间后注销用户

import { useFetcher } from "react-router-dom";
import { useFakeUserIsIdle } from "./fake/hooks";

export function useIdleLogout() {
  const fetcher = useFetcher();
  const userIsIdle = useFakeUserIsIdle();

  useEffect(() => {
    if (userIsIdle) {
      fetcher.submit(
        { idle: true },
        { method: "post", action: "/logout" }
      );
    }
  }, [userIsIdle]);
}

fetcher.submit 是对 fetcher 实例的 useSubmit 调用的包装器,因此它也接受与 useSubmit 相同的选项。

如果要提交到索引路由,请使用 ?index 参数

如果您发现自己在点击处理程序中调用此函数,您可能可以通过使用 <fetcher.Form> 来简化您的代码。

属性

fetcher.state

您可以使用 fetcher.state 来了解 fetcher 的状态。它将是以下之一:

  • idle - 没有任何内容正在获取。
  • submitting - 由于使用 POST、PUT、PATCH 或 DELETE 的 fetcher 提交,正在调用路由操作。
  • loading - fetcher 正在调用加载器(来自 fetcher.load)或在单独的提交或 useRevalidator 调用后正在重新验证。

fetcher.data

加载器或操作返回的数据存储在此处。一旦数据设置,它将保留在 fetcher 上,即使在重新加载和重新提交时也是如此。

function ProductDetails({ product }) {
  const fetcher = useFetcher();

  return (
    <details
      onToggle={(event) => {
        if (
          event.currentTarget.open &&
          fetcher.state === "idle" &&
          !fetcher.data
        ) {
          fetcher.load(`/product/${product.id}/details`);
        }
      }}
    >
      <summary>{product.name}</summary>
      {fetcher.data ? (
        <div>{fetcher.data}</div>
      ) : (
        <div>Loading product details...</div>
      )}
    </details>
  );
}

fetcher.formData

当使用 <fetcher.Form>fetcher.submit() 时,表单数据可用于构建乐观 UI。

function TaskCheckbox({ task }) {
  let fetcher = useFetcher();

  // while data is in flight, use that to immediately render
  // the state you expect the task to be in when the form
  // submission completes, instead of waiting for the
  // network to respond. When the network responds, the
  // formData will no longer be available and the UI will
  // use the value in `task.status` from the revalidation
  let status =
    fetcher.formData?.get("status") || task.status;

  let isComplete = status === "complete";

  return (
    <fetcher.Form method="post">
      <button
        type="submit"
        name="status"
        value={isComplete ? "complete" : "incomplete"}
      >
        {isComplete ? "Mark Complete" : "Mark Incomplete"}
      </button>
    </fetcher.Form>
  );
}

fetcher.json

当使用 fetcher.submit(data, { formEncType: "application/json" }) 时,提交的 JSON 可通过 fetcher.json 获取。

fetcher.text

当使用 fetcher.submit(data, { formEncType: "text/plain" }) 时,提交的文本可通过 fetcher.text 获取。

fetcher.formAction

告诉您表单提交到的操作 URL。

<fetcher.Form action="/mark-as-read" />;

// when the form is submitting
fetcher.formAction; // "mark-as-read"

fetcher.formMethod

告诉您正在提交的表单的方法:get、post、put、patch 或 delete。

<fetcher.Form method="post" />;

// when the form is submitting
fetcher.formMethod; // "post"

fetcher.formMethod 字段为小写,没有 future.v7_normalizeFormMethod 未来标志。这正在被规范化为大写以与 v7 中的 fetch() 行为保持一致,因此请升级您的 React Router v6 应用程序以采用大写 HTTP 方法。