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
中使用,所以它将被编译器从浏览器捆绑包中删除,如突出显示的行所示。
loader
中返回什么,都将公开给客户端,即使组件不渲染它。请像对待公共 API 端点一样谨慎对待您的 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
将知道它是一个字符串
json
,data.date
也知道它是一个字符串。当为客户端转换获取数据时,值会通过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
中最常见的用例是读取headers(如 Cookie)和请求中的 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这是一个完整的示例,展示了如何创建抛出响应的实用程序函数,以停止加载程序中的代码执行并显示备用 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>
);
}