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