你做不到😅。在客户端过渡期间,为了使你的应用尽可能快速,Remix 会并行调用所有加载器,并在单独的 fetch 请求中进行。每个加载器都需要进行自己的身份验证检查。
这可能与你在 Remix 之前所做的事情没有区别,只是现在可能更明显了。在 Remix 之外,当你向你的“API 路由”发出多个 fetch 请求时,每个端点都需要验证用户会话。换句话说,Remix 路由加载器本身就是“API 路由”,必须像对待 API 路由一样对待它们。
我们建议你创建一个验证用户会话的函数,可以将其添加到任何需要它的路由中。
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;
}
现在,在任何需要用户会话的加载器或操作中,你都可以调用该函数。
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
}
在 HTML 中,表单可以通过 action 属性发送到任何 URL,应用会导航到那里。
<Form action="/some/where" />
在 Remix 中,action 默认设置为渲染表单的路由,这使得 UI 和处理它的服务器代码可以轻松地放在一起。开发人员常常想知道在这种情况下如何处理多个 action。你有两种选择。
我们发现选项 (1) 最简单,因为你无需使用会话将验证错误返回到 UI。
HTML 按钮可以发送值,因此这是实现此功能最简单的方法。
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>
</>
);
}
如果你习惯于使用内容类型为application/json
的 fetch,你可能会想知道表单是如何融入其中的。FormData
与 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()
通常是你所需要的,我们鼓励你尝试一下!