React Router v7 已发布。 查看文档
Cookies
本页内容

Cookies

Cookie 是一小段信息,您的服务器在 HTTP 响应中将其发送给某人,他们的浏览器会在后续请求中将其发送回来。这项技术是许多交互式网站的基本构建块,它添加了状态,以便您可以构建身份验证(请参阅会话)、购物车、用户首选项以及许多其他需要记住“已登录”人员的功能。

Remix 的 Cookie 接口为 cookie 元数据提供了一个逻辑上可重用的容器。

使用 Cookie

虽然您可以手动创建这些 cookie,但更常见的是使用会话存储

在 Remix 中,您通常会在 loader 和/或 action 函数中使用 cookie(请参阅突变),因为这些是您需要读取和写入数据的地方。

假设您的电子商务网站上有一个横幅,提示用户查看您当前正在促销的商品。横幅跨越您主页的顶部,并在侧面包含一个按钮,允许用户关闭横幅,这样他们至少一周内不会看到它。

首先,创建一个 cookie

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

export const userPrefs = createCookie("user-prefs", {
  maxAge: 604_800, // one week
});

然后,您可以 import cookie 并在您的 loader 和/或 action 中使用它。在这种情况下,loader 只是检查用户偏好的值,因此您可以在您的组件中使用它来决定是否渲染横幅。当单击按钮时,<form> 在服务器上调用 action 并重新加载页面,而不显示横幅。

注意:我们建议(目前)您在 *.server.ts 文件中创建您的应用程序所需的所有 cookie,并将它们 import 到您的路由模块中。这允许 Remix 编译器正确地从不需要它们的浏览器构建中删除这些导入。我们希望最终消除这个警告。

import type {
  ActionFunctionArgs,
  LoaderFunctionArgs,
} from "@remix-run/node"; // or cloudflare/deno
import { json, redirect } from "@remix-run/node"; // or cloudflare/deno
import {
  useLoaderData,
  Link,
  Form,
} from "@remix-run/react";

import { userPrefs } from "~/cookies.server";

export async function loader({
  request,
}: LoaderFunctionArgs) {
  const cookieHeader = request.headers.get("Cookie");
  const cookie =
    (await userPrefs.parse(cookieHeader)) || {};
  return json({ showBanner: cookie.showBanner });
}

export async function action({
  request,
}: ActionFunctionArgs) {
  const cookieHeader = request.headers.get("Cookie");
  const cookie =
    (await userPrefs.parse(cookieHeader)) || {};
  const bodyParams = await request.formData();

  if (bodyParams.get("bannerVisibility") === "hidden") {
    cookie.showBanner = false;
  }

  return redirect("/", {
    headers: {
      "Set-Cookie": await userPrefs.serialize(cookie),
    },
  });
}

export default function Home() {
  const { showBanner } = useLoaderData<typeof loader>();

  return (
    <div>
      {showBanner ? (
        <div>
          <Link to="/sale">Don't miss our sale!</Link>
          <Form method="post">
            <input
              type="hidden"
              name="bannerVisibility"
              value="hidden"
            />
            <button type="submit">Hide</button>
          </Form>
        </div>
      ) : null}
      <h1>Welcome!</h1>
    </div>
  );
}

Cookie 具有多个属性,用于控制它们的过期时间、访问方式以及发送位置。这些属性中的任何一个都可以在 createCookie(name, options) 中指定,或者在生成 Set-Cookie 标头时在 serialize() 期间指定。

const cookie = createCookie("user-prefs", {
  // These are defaults for this cookie.
  path: "/",
  sameSite: "lax",
  httpOnly: true,
  secure: true,
  expires: new Date(Date.now() + 60_000),
  maxAge: 60,
});

// You can either use the defaults:
cookie.serialize(userPrefs);

// Or override individual ones as needed:
cookie.serialize(userPrefs, { sameSite: "strict" });

请阅读有关这些属性的更多信息,以便更好地了解它们的作用。

签名 Cookie

可以对 cookie 进行签名,以便在接收到 cookie 时自动验证其内容。由于伪造 HTTP 标头相对容易,因此对于您不希望任何人伪造的任何信息(如身份验证信息(请参阅会话)),这是一个好主意。

要对 cookie 进行签名,请在首次创建 cookie 时提供一个或多个 secrets

const cookie = createCookie("user-prefs", {
  secrets: ["s3cret1"],
});

具有一个或多个 secrets 的 cookie 将以确保 cookie 完整性的方式存储和验证。

可以通过在 secrets 数组的前面添加新的秘密来轮换秘密。使用旧秘密签名的 Cookie 仍然可以在 cookie.parse() 中成功解码,并且最新的秘密(数组中的第一个)将始终用于签名在 cookie.serialize() 中创建的传出 cookie。

export const cookie = createCookie("user-prefs", {
  secrets: ["n3wsecr3t", "olds3cret"],
});
import { cookie } from "~/cookies.server";

export async function loader({
  request,
}: LoaderFunctionArgs) {
  const oldCookie = request.headers.get("Cookie");
  // oldCookie may have been signed with "olds3cret", but still parses ok
  const value = await cookie.parse(oldCookie);

  new Response("...", {
    headers: {
      // Set-Cookie is signed with "n3wsecr3t"
      "Set-Cookie": await cookie.serialize(value),
    },
  });
}

createCookie

创建一个逻辑容器,用于从服务器管理浏览器 cookie。

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

const cookie = createCookie("cookie-name", {
  // all of these are optional defaults that can be overridden at runtime
  expires: new Date(Date.now() + 60_000),
  httpOnly: true,
  maxAge: 60,
  path: "/",
  sameSite: "lax",
  secrets: ["s3cret1"],
  secure: true,
});

要了解有关每个属性的更多信息,请参阅MDN Set-Cookie 文档

isCookie

如果对象是 Remix cookie 容器,则返回 true

import { isCookie } from "@remix-run/node"; // or cloudflare/deno
const cookie = createCookie("user-prefs");
console.log(isCookie(cookie));
// true

cookie 容器从 createCookie 返回,并具有一些属性和方法。

const cookie = createCookie(name);
cookie.name;
cookie.parse();
// etc.

cookie.name

cookie 的名称,在 CookieSet-Cookie HTTP 标头中使用。

cookie.parse()

提取并返回给定 Cookie 标头中此 cookie 的值。

const value = await cookie.parse(
  request.headers.get("Cookie")
);

cookie.serialize()

序列化一个值,并将其与此 cookie 的选项组合,以创建一个 Set-Cookie 标头,适合在传出的 Response 中使用。

new Response("...", {
  headers: {
    "Set-Cookie": await cookie.serialize({
      showBanner: true,
    }),
  },
});

cookie.isSigned

如果 cookie 使用任何 secrets,则将为 true,否则为 false

let cookie = createCookie("user-prefs");
console.log(cookie.isSigned); // false

cookie = createCookie("user-prefs", {
  secrets: ["soopersekrit"],
});
console.log(cookie.isSigned); // true

cookie.expires

此 cookie 过期的 Date。请注意,如果 cookie 同时具有 maxAgeexpires,则此值将是当前时间加上 maxAge 值的日期,因为 Max-Age 的优先级高于 Expires

const cookie = createCookie("user-prefs", {
  expires: new Date("2021-01-01"),
});

console.log(cookie.expires); // "2020-01-01T00:00:00.000Z"
文档和示例根据 MIT