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 方法。