如何在 React Router v7 中为不同区域设置格式化日期

以区域特定的格式显示日期

问题

日期没有统一的书写格式。数字序列 10/12/2025 在美国表示 10 月 12 日,但在英国表示 12 月 10 日。即使是像 "Oct 12, 2025" 这样的格式,也假设了英语的月份名称和特定的排列顺序,这可能与用户的期望不符。当应用程序以单一固定格式显示日期时,对于遵循不同惯例的地区用户来说,会显得陌生,从而降低清晰度和信任感。

不同的语言环境对日期呈现有不同的规则,包括日、月、年的顺序,分隔符的选择,以及月份名称是全写还是缩写。忽视这些惯例会迫使用户在脑海中将日期转换为他们熟悉的格式,从而增加认知负担和误解的风险。

解决方案

根据用户的语言环境格式化日期值,将显示逻辑委托给理解区域惯例的国际化 API。与其手动构建日期字符串,不如将日期对象传递给格式化函数或组件,这些函数或组件会根据活动语言环境应用正确的顺序、分隔符和月份名称。

React-intl 提供了声明式组件和命令式方法,这些方法接受标准的日期格式选项并生成符合语言环境的字符串。通过指定要包含的日期部分及其详细程度,您可以控制输出样式,而库会处理区域差异。这种方法将日期格式化逻辑与显示组件分离,并确保整个应用程序的一致性。

步骤

1. 使用 FormattedDate 在组件中格式化日期

使用 react-intl 的 FormattedDate 组件以语言环境特定的格式渲染日期。该组件接受一个日期值和与 Intl.DateTimeFormatOptions 对应的格式选项。

import { FormattedDate } from "react-intl";

export default function EventCard({ event }) {
  return (
    <article>
      <h2>{event.title}</h2>
      <time>
        <FormattedDate
          value={event.date}
          year="numeric"
          month="long"
          day="numeric"
        />
      </time>
      <p>{event.description}</p>
    </article>
  );
}

FormattedDate 组件使用 formatDateIntl.DateTimeFormat API 生成与周围 IntlProvider 提供的语言环境匹配的字符串。yearmonthday 选项控制显示的部分及其格式。

2. 使用 useIntl 以命令式格式化日期

使用 useIntl 钩子访问 formatDate 方法,当您需要为非渲染上下文(如属性、aria 标签或数据转换)生成格式化的日期字符串时。

import { useIntl } from "react-intl";

export default function EventList({ events }) {
  const intl = useIntl();

  return (
    <ul>
      {events.map((event) => {
        const formattedDate = intl.formatDate(event.date, {
          year: "numeric",
          month: "short",
          day: "numeric",
        });

        return (
          <li key={event.id}>
            <a
              href={`/events/${event.id}`}
              aria-label={`${event.title} on ${formattedDate}`}
            >
              {event.title}
            </a>
          </li>
        );
      })}
    </ul>
  );
}

formatDate 函数接受一个日期值和可选的 Intl.DateTimeFormatOptions,并返回一个按语言环境格式化的字符串。当格式化日期需要嵌入到另一个字符串中或用作属性值时,这非常有用。

3. 创建可重用的日期格式化助手

将常见的日期格式化模式提取到一个包装 intl.formatDate 的助手函数中,以确保整个应用程序的一致性。

import { useIntl } from "react-intl";

export function useDateFormatter() {
  const intl = useIntl();

  return {
    formatShortDate: (date: Date | number) =>
      intl.formatDate(date, {
        year: "numeric",
        month: "numeric",
        day: "numeric",
      }),

    formatLongDate: (date: Date | number) =>
      intl.formatDate(date, {
        year: "numeric",
        month: "long",
        day: "numeric",
      }),

    formatDateTime: (date: Date | number) =>
      intl.formatDate(date, {
        year: "numeric",
        month: "short",
        day: "numeric",
        hour: "numeric",
        minute: "2-digit",
      }),
  };
}

此钩子集中格式化逻辑,并使整个应用程序中应用一致的日期样式变得容易。组件根据所需的详细程度调用适当的方法。

4. 在路由加载器中格式化日期

在数据加载期间需要格式化日期时,可以从请求中获取区域设置,并使用 createIntl 在返回加载器数据之前格式化日期。

import type { Route } from "./+types/event";
import { createIntl, createIntlCache } from "react-intl";

const cache = createIntlCache();

export async function loader({ request }: Route.LoaderArgs) {
  const url = new URL(request.url);
  const locale = url.searchParams.get("locale") || "en";

  const event = await fetchEvent();

  const intl = createIntl({ locale, messages: {} }, cache);

  return {
    event,
    formattedDate: intl.formatDate(event.date, {
      year: "numeric",
      month: "long",
      day: "numeric",
    }),
  };
}

export default function Event({ loaderData }: Route.ComponentProps) {
  return (
    <article>
      <h1>{loaderData.event.title}</h1>
      <time>{loaderData.formattedDate}</time>
    </article>
  );
}

createIntl 函数创建了一个 intl 对象,可以在 React 组件之外格式化日期。这种方法在服务器端或静态生成期间预格式化日期,从而减少了客户端的工作量。