コンテンツにスキップ

FAQs

このコンテンツはまだ日本語訳がありません。

常见问题

我如何让父路由加载器验证用户并保护所有子路由?

你不能 😅。在客户端转换期间,为了让你的应用尽可能快,Remix 将在单独的获取请求中并行调用所有加载器。每个加载器都需要进行自己的身份验证检查。

这可能与您在 Remix 之前所做的事情没有什么不同,只是现在可能更加明显了。在 Remix 之外,当您多次提取API 路由时,每个端点都需要验证用户会话。换句话说,Remix 路由加载器是它们自己的API 路由,必须如此对待。

我们建议您创建一个验证用户会话的函数,该函数可以添加到任何需要它的路由中。

app/session.ts
import {
createCookieSessionStorage,
redirect,
} from "@remix-run/node"; // or cloudflare/deno
// somewhere you've got a session storage
const { getSession } = createCookieSessionStorage();
export async function requireUserSession(request) {
// get the session
const cookie = request.headers.get("cookie");
const session = await getSession(cookie);
// validate the session, `userId` is just an example, use whatever value you
// put in the session when the user authenticated
if (!session.has("userId")) {
// if there is no user session, redirect to login
throw redirect("/login");
}
return session;
}

现在,在任何需要用户会话的加载器或操作中,您都可以调用该函数。

app/routes/projects.tsx
export async function loader({
request,
}: LoaderFunctionArgs) {
// if the user isn't authenticated, this will redirect to login
const session = await requireUserSession(request);
// otherwise the code continues to execute
const projects = await fakeDb.projects.scan({
userId: session.get("userId"),
});
return json(projects);
}

即使你不需要会话信息,该函数仍然会保护路由:

export async function loader({
request,
}: LoaderFunctionArgs) {
await requireUserSession(request);
// continue
}

如何在一条路由中处理多个表格?

在 YouTube 上观看

在 HTML 中,表单可以使用 action 属性发布到任何 URL,应用程序将导航到那里:

<Form action="/some/where" />

在 Remix 中,操作默认为表单呈现的路由,这样可以轻松地将 UI 和处理它的服务器代码放在一起。开发人员经常想知道在这种情况下如何处理多个操作。您有两种选择:

  1. 发送表单字段以确定您要采取的操作
  2. 发布到不同的路由并重定向回原始路由

我们发现选项(1)是最简单的,因为您不必处理会话来将验证错误返回到 UI。

HTML 按钮可以发送一个值,所以这是实现这一点的最简单的方法:

// app/routes/projects.$id.tsx
export async function action({
request,
}: ActionFunctionArgs) {
const formData = await request.formData();
const intent = formData.get("intent");
switch (intent) {
case "update": {
// do your update
return updateProjectName(formData.get("name"));
}
case "delete": {
// do your delete
return deleteStuff(formData);
}
default: {
throw new Error("Unexpected action");
}
}
}
export default function Projects() {
const project = useLoaderData<typeof loader>();
return (
<>
<h2>Update Project</h2>
<Form method="post">
<label>
Project name:{" "}
<input
type="text"
name="name"
defaultValue={project.name}
/>
</label>
<button type="submit" name="intent" value="update">
Update
</button>
</Form>
<Form method="post">
<button type="submit" name="intent" value="delete">
Delete
</button>
</Form>
</>
);
}

较旧的浏览器版本可能会破坏此功能,因为它们可能不支持 SubmitEvent:submitter 属性FormData() 构造函数 submitter 参数。请务必检查这些功能的浏览器兼容性。如果您需要对此进行 polyfill,请参阅 Event Submitter PolyfillFormData Submitter Polyfill。有关更多详细信息,请参阅相关问题 remix-run/remix#9704

如何在表单中获取结构化数据?

如果您习惯使用内容类型 application/json 进行获取,您可能会想知道表单如何适应这种情况。FormData 与 JSON 略有不同。

  • 它不能有嵌套数据,它只是键值
  • 与 JSON 不同,它可以在一个键上有多个条目。

如果您只想将结构化数据发送到帖子数组,则可以在多个输入上使用相同的键:

<Form method="post">
<p>Select the categories for this video:</p>
<label>
<input type="checkbox" name="category" value="comedy" />{" "}
Comedy
</label>
<label>
<input type="checkbox" name="category" value="music" />{" "}
Music
</label>
<label>
<input type="checkbox" name="category" value="howto" />{" "}
How-To
</label>
</Form>

每个复选框的名称为:category。由于 FormData 可以在同一个键上有多个值,因此您不需要 JSON。在您的操作中使用 formData.getAll() 访问复选框值。

export async function action({
request,
}: ActionFunctionArgs) {
const formData = await request.formData();
const categories = formData.getAll("category");
// ["comedy", "music"]
}

使用相同的输入名称和formData.getAll()可以涵盖想要在表单中提交结构化数据的大多数情况。

如果您仍想提交嵌套结构,则可以使用非标准表单字段命名约定和 npm 中的 query-string 包:

<>
// arrays with []
<input name="category[]" value="comedy" />
<input name="category[]" value="comedy" />
// nested structures parentKey[childKey]
<input name="user[name]" value="Ryan" />
</>

然后在你的行动中:

import queryString from "query-string";
// in your action:
export async function action({
request,
}: ActionFunctionArgs) {
// use `request.text()`, not `request.formData` to get the form data as a url
// encoded form query string
const formQueryString = await request.text();
// parse it into an object
const obj = queryString.parse(formQueryString);
}

有些人甚至将 JSON 转储到隐藏字段中。请注意,这种方法不适用于渐进式增强。如果这对您的应用并不重要,这是一种发送结构化数据的简单方法。

<input
type="hidden"
name="json"
value={JSON.stringify(obj)}
/>

然后在动作中解析它:

export async function action({
request,
}: ActionFunctionArgs) {
const formData = await request.formData();
const obj = JSON.parse(formData.get("json"));
}

再次强调,formData.getAll()通常就是您所需要的,我们鼓励您尝试一下!