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

action

路由操作是“写入”到路由 加载器 的“读取”。它们为应用程序提供了一种使用简单的 HTML 和 HTTP 语义执行数据变异的方法,而 React Router 则抽象了异步 UI 和重新验证的复杂性。这为您提供了 HTML + HTTP 的简单心理模型(浏览器处理异步和重新验证),并具有现代 SPA 的行为和 UX 功能。

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

<Route
  path="/song/:songId/edit"
  element={<EditSong />}
  action={async ({ params, request }) => {
    let formData = await request.formData();
    return fakeUpdateSong(params.songId, formData);
  }}
  loader={({ params }) => {
    return fakeGetSong(params.songId);
  }}
/>

每当应用程序向您的路由发送非 GET 提交(“post”、“put”、“patch”、“delete”)时,都会调用操作。这可以通过几种方式发生

// forms
<Form method="post" action="/songs" />;
<fetcher.Form method="put" action="/songs/123/edit" />;

// imperative submissions
let submit = useSubmit();
submit(data, {
  method: "delete",
  action: "/songs/123",
});
fetcher.submit(data, {
  method: "patch",
  action: "/songs/123/edit",
});

params

路由参数从 动态段 中解析并传递到您的操作。这对于确定要变异的资源很有用

<Route
  path="/projects/:projectId/delete"
  action={({ params }) => {
    return fakeDeleteProject(params.projectId);
  }}
/>

request

这是一个发送到您的路由的 Fetch Request 实例。最常见的用例是从请求中解析 FormData

<Route
  action={async ({ request }) => {
    let formData = await request.formData();
    // ...
  }}
/>

请求?!

乍一看,操作接收“请求”可能很奇怪。您是否曾经写过以下代码?

<form
  onSubmit={(event) => {
    event.preventDefault();
    // ...
  }}
/>

您到底在阻止什么?

如果没有 JavaScript,只有纯 HTML 和 HTTP Web 服务器,那么被阻止的默认事件实际上非常棒。浏览器会将表单中的所有数据序列化为 FormData 并将其作为新请求的主体发送到您的服务器。与上面的代码类似,React Router <Form> 会阻止浏览器发送该请求,而是将请求发送到您的路由操作!这使得高度动态的 Web 应用程序能够使用 HTML 和 HTTP 的简单模型。

请记住,formData 中的值会自动从表单提交中序列化,因此您的输入需要一个 name

<Form method="post">
  <input name="songTitle" />
  <textarea name="lyrics" />
  <button type="submit">Save</button>
</Form>;

// accessed by the same names
formData.get("songTitle");
formData.get("lyrics");

有关 formData 的更多信息,请参阅 使用 FormData

选择加入序列化类型

请注意,当使用 useSubmit 时,您也可以传递 encType: "application/json"encType: "text/plain" 来将您的有效负载序列化为 request.json()request.text()

返回响应

虽然您可以从操作中返回任何您想要的东西,并从 useActionData 中访问它,但您也可以返回一个 Web Response

有关更多信息,请参阅 加载器文档

在操作中抛出

您可以在操作中 throw 以退出当前调用堆栈(停止运行当前代码),React Router 将从“错误路径”重新开始。

<Route
  action={async ({ params, request }) => {
    const res = await fetch(
      `/api/properties/${params.id}`,
      {
        method: "put",
        body: await request.formData(),
      }
    );
    if (!res.ok) throw res;
    return { ok: true };
  }}
/>

有关更多详细信息和扩展用例,请阅读 errorElement 文档。

处理每个路由的多个操作

一个相当常见的问题是“如果我需要在我的操作中处理多个不同的行为怎么办?” 有几种方法可以实现这一点,但通常最简单的方法是在您的 <button type="submit"> 上放置一个 name/value,并在操作中使用它来决定要执行的代码(没错 - 提交 按钮 可以具有 name/value 属性!)

async function action({ request }) {
  let formData = await request.formData();
  let intent = formData.get("intent");

  if (intent === "edit") {
    await editSong(formData);
    return { ok: true };
  }

  if (intent === "add") {
    await addSong(formData);
    return { ok: true };
  }

  throw json(
    { message: "Invalid intent" },
    { status: 400 }
  );
}

function Component() {
  let song = useLoaderData();

  // When the song exists, show an edit form
  if (song) {
    return (
      <Form method="post">
        <p>Edit song lyrics:</p>
        {/* Edit song inputs */}
        <button type="submit" name="intent" value="edit">
          Edit
        </button>
      </Form>
    );
  }

  // Otherwise show a form to add a new song
  return (
    <Form method="post">
      <p>Add new lyrics:</p>
      {/* Add song inputs */}
      <button type="submit" name="intent" value="add">
        Add
      </button>
    </Form>
  );
}

如果按钮名称/值不适合您的用例,您也可以使用隐藏输入来发送 intent,或者您可以通过 <Form method> 属性(POST 用于添加,PUT/PATCH 用于编辑,DELETE 用于删除)提交不同的 HTTP 方法。