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

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

问题

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

不同地区在日期展示上有各自的规则,包括日、月、年顺序,分隔符的选择,以及月份名称是全称还是缩写。如果忽略这些习惯,用户就不得不在脑海中将日期转换为熟悉的格式,这会增加认知负担并提高误解的风险。

解决方案

根据用户的地区设置格式化日期值,将展示逻辑交给了解地区规范的国际化 API 处理。不要手动拼接日期字符串,而是将日期对象传递给格式化函数或组件,由其根据当前地区自动应用正确的顺序、分隔符和月份名称。

React-intl 提供了声明式组件和命令式方法,支持标准日期格式化选项,并生成符合地区规范的字符串。通过指定需要包含的日期部分及其详细程度,你可以控制输出样式,而库会自动处理地区差异。这种方式将日期格式化逻辑与展示组件分离,确保应用内的一致性。

步骤

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

使用 FormattedDate 组件(来自 react-intl)可以根据本地化格式渲染日期。该组件接收一个日期值和与 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 命令式格式化日期

当你需要为属性、aria 标签或数据转换等非渲染场景获取格式化日期字符串时,可以使用 useIntl hook 获取 formatDate 方法。

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,返回本地化格式的字符串。当需要将格式化后的日期嵌入其他字符串或作为 prop 值时,这非常有用。

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

该 hook 集中管理格式化逻辑,便于在应用中统一应用日期样式。组件可根据所需的详细程度调用相应方法。

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

当数据加载过程中需要格式化日期时,可从请求中获取 locale,并使用 createIntl 在返回 loader 数据前完成日期格式化。

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 组件之外格式化日期。这种方法可以在服务器端或静态生成期间预先格式化日期,从而减少客户端的处理工作。