loader
On this page

loader

关注 📼 Remix Single: Loading data into components

每个路由都可以定义一个 loader 函数,用于在渲染时向路由提供数据。

¥Each route can define a loader function that provides data to the route when rendering.

import { json } from "@remix-run/node"; // or cloudflare/deno

export const loader = async () => {
  return json({ ok: true });
};

此函数仅在服务器端运行。在初始服务器渲染时,它会将数据提供给 HTML 文档。在浏览器中导航时,Remix 将通过浏览器中的 fetch 调用该函数。

¥This function is only ever run on the server. On the initial server render, it will provide data to the HTML document. On navigations in the browser, Remix will call the function via fetch from the browser.

这意味着你可以直接与数据库通信,使用仅限服务器的 API 密钥等。任何未用于渲染 UI 的代码都将从浏览器打包包中删除。

¥This means you can talk directly to your database, use server-only API secrets, etc. Any code not used to render the UI will be removed from the browser bundle.

以数据库 ORM Prisma 为例:

¥Using the database ORM Prisma as an example:

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 中使用,因此它将被编译器从浏览器包中删除,如高亮的行所示。

¥Because prisma is only used in the loader it will be removed from the browser bundle by the compiler, as illustrated by the highlighted lines.

请注意,即使组件未渲染,从 loader 返回的任何内容都将暴露给客户端。请像对待公共 API 端点一样谨慎对待你的 loader

类型安全

¥Type Safety

你可以使用 useLoaderData<typeof loader> 通过网络为你的 loader 和组件获取类型安全。

¥You can get type safety over the network for your loader and component with useLoaderData<typeof 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.name will know that it's a string

  • 即使我们向 json 传递了一个日期对象,data.date 也能识别出它是一个字符串。当为客户端转换获取数据时,值会使用 JSON.stringify 通过网络进行序列化,并且类型会感知到这一点。

    ¥data.date will also know that it's a string even though we passed a date object to json. When data is fetched for client transitions, the values are serialized over the network with JSON.stringify, and the types are aware of that

params

路由参数由路由文件名定义。如果某个片段以 $ 开头,例如 $invoiceId,则该片段 URL 的值将传递给你的 loader

¥Route params are defined by route file names. If a segment begins with $ like $invoiceId, the value from the URL for that segment will be passed to your loader.

// if the user visits /invoices/123
export async function loader({
  params,
}: LoaderFunctionArgs) {
  params.invoiceId; // "123"
}

参数主要用于通过 ID 查找记录:

¥Params are mostly useful for looking up records by 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

这是一个 获取请求 实例。你可以阅读 MDN 文档以了解其所有属性。

¥This is a Fetch Request instance. You can read the MDN docs to see all of its properties.

loader 中最常见的用例是从请求中读取 headers(如 Cookie)和 URL URLSearchParams

¥The most common use cases in loaders are reading headers (like cookies) and URL URLSearchParams from the request:

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 应用之间差距的方法。

¥This is the context passed in to your server adapter's getLoadContext() function. It's a way to bridge the gap between the adapter's request/response API with your Remix app.

此 API 是一个应急方案,通常不需要它

以 Express 适配器为例:

¥Using the express adapter as an example:

const {
  createRequestHandler,
} = require("@remix-run/express");

app.all(
  "*",
  createRequestHandler({
    getLoadContext(req, res) {
      // this becomes the loader context
      return { expressUser: req.user };
    },
  })
);

然后你的 loader 就可以访问它了。

¥And then your loader can access it.

export async function loader({
  context,
}: LoaderFunctionArgs) {
  const { expressUser } = context;
  // ...
}

返回的响应实例

¥Returning Response Instances

你需要从 loader 返回 获取响应

¥You need to return a Fetch Response from your loader.

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 助手 可以简化这一过程,因此你无需自行构建它们,但这两个示例实际上是相同的!

¥Using the json helper simplifies this, so you don't have to construct them yourself, but these two examples are effectively the same!

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 助手在响应中添加标头或状态码:

¥You can see how json just does a little of the work to make your loader a lot cleaner. You can also use the json helper to add headers or a status code to your response:

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);
};

另请参阅:

¥See also:

在 Loader 中抛出响应

¥Throwing Responses in Loaders

除了返回响应之外,你还可以从 loader 中抛出 Response 对象。这允许你突破调用堆栈并执行以下两项操作之一:

¥Along with returning responses, you can also throw Response objects from your loaders. This allows you to break through the call stack and do one of two things:

  • 重定向到另一个 URL

    ¥Redirect to another URL

  • 通过 ErrorBoundary 显示包含上下文数据的替代 UI

    ¥Show an alternate UI with contextual data through the ErrorBoundary

以下是一个完整的示例,展示了如何创建实用函数,该函数会抛出响应以停止加载器中的代码执行并显示另一个 UI。

¥Here is a full example showing how you can create utility functions that throw responses to stop code execution in the loader and show an alternative 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>
  );
}
Remix v2.17 中文网 - 粤ICP备13048890号