loader
每个路由都可以定义一个 loader
函数,该函数在渲染时为路由提供数据。
import { json } from "@remix-run/node"; // or cloudflare/deno
export const loader = async () => {
return json({ ok: true });
};
此函数仅在服务器上运行。在初始服务器渲染时,它将为 HTML 文档提供数据。在浏览器中的导航中,Remix 将通过浏览器中的fetch
调用该函数。
这意味着您可以直接与数据库对话,使用仅限服务器的 API 密钥等。任何不用于渲染 UI 的代码都将从浏览器包中删除。
以数据库 ORM Prisma 为例
import { useLoaderData } from "@remix-run/react";
import { prisma } from "../db";
export async function loader() {
return json(await prisma.user.findMany());
}
export default function Users() {
const data = useLoaderData<typeof loader>();
return (
<ul>
{data.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
因为 prisma
仅在 loader
中使用,所以编译器会将其从浏览器包中删除,如高亮显示的代码行所示。
您可以使用 useLoaderData<typeof loader>
获取 loader
和组件在网络上的类型安全。
import { json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
export async function loader() {
return json({ name: "Ryan", date: new Date() });
}
export default function SomeRoute() {
const data = useLoaderData<typeof loader>();
}
data.name
将知道它是一个字符串data.date
也将知道它是一个字符串,即使我们将日期对象传递给 json
。当为客户端转换提取数据时,值将通过 JSON.stringify
在网络上序列化,并且类型会意识到这一点params
路由参数由路由文件名定义。如果一个段以 $
开头,如 $invoiceId
,则该段的 URL 值将传递给您的 loader
。
// if the user visits /invoices/123
export async function loader({
params,
}: LoaderFunctionArgs) {
params.invoiceId; // "123"
}
参数主要用于按 ID 查找记录
// if the user visits /invoices/123
export async function loader({
params,
}: LoaderFunctionArgs) {
const invoice = await fakeDb.getInvoice(params.invoiceId);
if (!invoice) throw new Response("", { status: 404 });
return json(invoice);
}
request
这是一个 Fetch Request 实例。您可以阅读 MDN 文档以查看其所有属性。
loader
中最常见的用例是从请求中读取 标头(如 cookies)和 URL URLSearchParams
export async function loader({
request,
}: LoaderFunctionArgs) {
// read a cookie
const cookie = request.headers.get("Cookie");
// parse the search params for `?q=`
const url = new URL(request.url);
const query = url.searchParams.get("q");
}
context
这是传递到您的服务器适配器的 getLoadContext()
函数的上下文。这是一种桥接适配器的请求/响应 API 与您的 Remix 应用程序之间差距的方法。
以 express 适配器为例
const {
createRequestHandler,
} = require("@remix-run/express");
app.all(
"*",
createRequestHandler({
getLoadContext(req, res) {
// this becomes the loader context
return { expressUser: req.user };
},
})
);
然后您的 loader
可以访问它。
export async function loader({
context,
}: LoaderFunctionArgs) {
const { expressUser } = context;
// ...
}
您需要从 loader
返回一个 Fetch Response。
export async function loader() {
const users = await db.users.findMany();
const body = JSON.stringify(users);
return new Response(body, {
headers: {
"Content-Type": "application/json",
},
});
}
使用 json
辅助函数 可以简化此过程,因此您不必自己构造它们,但这两个示例实际上是相同的!
import { json } from "@remix-run/node"; // or cloudflare/deno
export const loader = async () => {
const users = await fakeDb.users.findMany();
return json(users);
};
您可以看到 json
只是做了一些工作来使您的 loader
更加简洁。您还可以使用 json
辅助函数向响应添加标头或状态代码
import { json } from "@remix-run/node"; // or cloudflare/deno
export const loader = async ({
params,
}: LoaderFunctionArgs) => {
const project = await fakeDb.project.findOne({
where: { id: params.id },
});
if (!project) {
return json("Project not found", { status: 404 });
}
return json(project);
};
另请参阅
除了返回响应之外,你也可以从你的 loader
中抛出 Response
对象。 这允许你打破调用堆栈,并执行以下两件事之一
ErrorBoundary
显示带有上下文数据的替代 UI这里有一个完整的例子,展示了如何创建实用程序函数,这些函数会抛出响应来停止 loader 中的代码执行并显示替代 UI。
import { json } from "@remix-run/node"; // or cloudflare/deno
export function getInvoice(id) {
const invoice = db.invoice.find({ where: { id } });
if (invoice === null) {
throw json("Not Found", { status: 404 });
}
return invoice;
}
import { redirect } from "@remix-run/node"; // or cloudflare/deno
import { getSession } from "./session";
export async function requireUserSession(request) {
const session = await getSession(
request.headers.get("cookie")
);
if (!session) {
// You can throw our helpers like `redirect` and `json` because they
// return `Response` objects. A `redirect` response will redirect to
// another URL, while other responses will trigger the UI rendered
// in the `ErrorBoundary`.
throw redirect("/login", 302);
}
return session.get("user");
}
import type { LoaderFunctionArgs } from "@remix-run/node"; // or cloudflare/deno
import { json } from "@remix-run/node"; // or cloudflare/deno
import {
isRouteErrorResponse,
useLoaderData,
useRouteError,
} from "@remix-run/react";
import { getInvoice } from "~/db";
import { requireUserSession } from "~/http";
export const loader = async ({
params,
request,
}: LoaderFunctionArgs) => {
const user = await requireUserSession(request);
const invoice = getInvoice(params.invoiceId);
if (!invoice.userIds.includes(user.id)) {
throw json(
{ invoiceOwnerEmail: invoice.owner.email },
{ status: 401 }
);
}
return json(invoice);
};
export default function InvoiceRoute() {
const invoice = useLoaderData<typeof loader>();
return <InvoiceView invoice={invoice} />;
}
export function ErrorBoundary() {
const error = useRouteError();
if (isRouteErrorResponse(error)) {
switch (error.status) {
case 401:
return (
<div>
<p>You don't have access to this invoice.</p>
<p>
Contact {error.data.invoiceOwnerEmail} to get
access
</p>
</div>
);
case 404:
return <div>Invoice not found!</div>;
}
return (
<div>
Something went wrong: {error.status}{" "}
{error.statusText}
</div>
);
}
return (
<div>
Something went wrong:{" "}
{error?.message || "Unknown Error"}
</div>
);
}