FAQs
このコンテンツはまだ日本語訳がありません。
常见问题
我如何让父路由加载器验证用户并保护所有子路由?
你不能 😅。在客户端转换期间,为了让你的应用尽可能快,Remix 将在单独的获取请求中并行调用所有加载器。每个加载器都需要进行自己的身份验证检查。
这可能与您在 Remix 之前所做的事情没有什么不同,只是现在可能更加明显了。在 Remix 之外,当您多次提取API 路由
时,每个端点都需要验证用户会话。换句话说,Remix 路由加载器是它们自己的API 路由
,必须如此对待。
我们建议您创建一个验证用户会话的函数,该函数可以添加到任何需要它的路由中。
import { createCookieSessionStorage, redirect,} from "@remix-run/node"; // or cloudflare/deno
// somewhere you've got a session storageconst { 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 中,操作默认为表单呈现的路由,这样可以轻松地将 UI 和处理它的服务器代码放在一起。开发人员经常想知道在这种情况下如何处理多个操作。您有两种选择:
- 发送表单字段以确定您要采取的操作
- 发布到不同的路由并重定向回原始路由
我们发现选项(1)是最简单的,因为您不必处理会话来将验证错误返回到 UI。
HTML 按钮可以发送一个值,所以这是实现这一点的最简单的方法:
// app/routes/projects.$id.tsxexport 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 Polyfill 和 FormData 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()
通常就是您所需要的,我们鼓励您尝试一下!