主程式
分支
主程式 (6.23.1)開發
版本
6.23.1v4/5.xv3.x
動作
在本頁

action

路由動作是路由 loader「讀」的「寫」。它們提供一種方法,讓應用程式可以透過簡單的 HTML 和 HTTP 語意來執行資料變更,同時 React Router 移除非同步 UI 和重新驗證的複雜性。這提供了簡單的 HTML + HTTP 心智模型(瀏覽器處理非同步和重新驗證),以及現代單頁應用程式的行為和 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

這是傳送到路由的 提取要求 實例。最常見的用例是從請求中分析 FormData

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

一個請求?

一開始看到動作收到「要求」可能有點奇怪。你寫過以下程式碼嗎?

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

你究竟防範了什麼?

如果沒有 JavaScript,只有純 HTML 和 HTTP 網路伺服器,預設被阻止的該事件其實很超棒。瀏覽器會將表單中的所有資料序列化為 FormData,並將其作為新要求的主體傳送至伺服器。如同上述程式碼,React Router <Form> 會阻止瀏覽器傳送該要求,而是將要求傳送至路徑動作!這讓簡單的 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" ,而將您的 payload 序列化到 request.json()request.text()

返回回應

儘管您可以從動作返回任何想要的資料,並透過 useActionData 存取它們,但您也可以返回網路 回應

有關詳細資訊,請參閱 載入器文件

在動作中中斷

您可以在動作中進行中斷 (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>
  );
}

如果按鈕的 name/value 不適合您的使用案例,您也可以使用隱藏輸入欄位來傳送 intent,或是透過 <Form method> 屬性 (POST 用於增加,PUT/PATCH 用於編輯,DELETE 用於移除) 送出不同的 HTTP 方法。