加载器
本页内容

loader

观看 📼 Remix 单曲将数据加载到组件中

每个路由都可以定义一个 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 将知道它是一个字符串

  • 即使我们将日期对象传递给了jsondata.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 应用程序之间架起桥梁的方法。

此 API 是一个逃生舱,很少需要它。

以 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;
  // ...
}

返回 Response 实例

您需要从 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

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

  • 重定向到另一个 URL
  • 通过 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>
  );
}
文档和示例根据以下许可获得许可 MIT