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
中最常見的用例是讀取 headers (如 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()
函式中的 context。它是彌合適配器的請求/回應 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這是一個完整的範例,展示了如何建立拋出 responses 的工具函式,以停止 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>
);
}