如何在 React Router v7 中为不同地区格式化数字

使用地区特定的分隔符显示数字

问题

世界各地的数字书写方式各不相同。在美国,10,000.5 表示一万零零点五,而在德国则写作 10.000,5——逗号和句号的作用完全对调。这不仅仅是风格或偏好的问题,而是可读性的问题。德国用户看到 10,000.5 可能会把它读作十,忽略分组分隔符。美国用户看到 10.000,5 可能会把它读作一万,忽略小数点。

相同的数字在不同地区的读者眼中可能表达完全相反的含义。如果应用程序在没有考虑地区格式的情况下直接显示原始数字,用户很容易产生误解,甚至对数据的可信度产生怀疑。

解决方案

根据用户的地区格式化数字,遵循本地的千位分隔符和小数点规则。这样可以将数字值转换为用户熟悉的格式,提高可读性和准确性。

React-intl 提供了格式化方法,利用浏览器内置的 Intl.NumberFormat API,自动处理不同地区的数字格式。通过这些格式化器传递数字值,应用程序无需手动处理分隔符逻辑,就能输出符合用户期望的结果。

步骤

1. 使用 useIntl 创建数字格式化组件

useIntl 钩子提供了包括 formatNumber 在内的格式化方法,可以接收数字并返回符合地区格式的字符串。

import { useIntl } from "react-intl";

export default function ProductPrice() {
  const intl = useIntl();
  const price = 1234.56;

  return (
    <div>
      <p>Price: {intl.formatNumber(price)}</p>
    </div>
  );
}

formatNumber 方法会根据当前地区自动应用正确的分隔符。例如,地区为 en-US 的用户会看到“1,234.56”,而地区为 de-DE 的用户会看到“1.234,56”。

2. 使用样式选项格式化货币数值

formatNumber 方法接受符合 Intl.NumberFormatOptions 的选项,包括货币格式化。

import { useIntl } from "react-intl";

export default function ProductPrice() {
  const intl = useIntl();
  const price = 1234.56;

  return (
    <div>
      <p>
        {intl.formatNumber(price, {
          style: "currency",
          currency: "USD",
        })}
      </p>
    </div>
  );
}

对于 en-US,会生成 “$1,234.56”;对于 de-DE,会生成 “1.234,56 $”,分别应用了分隔符和货币符号的本地化规范。

3. 使用 FormattedNumber 进行声明式格式化

FormattedNumber 组件提供了声明式的替代方案,支持以 props 方式传递相同的选项。

import { FormattedNumber } from "react-intl";

export default function Statistics() {
  const totalUsers = 1500000;
  const growthRate = 0.23;

  return (
    <div>
      <p>
        Total users: <FormattedNumber value={totalUsers} />
      </p>
      <p>
        Growth rate: <FormattedNumber value={growthRate} style="percent" />
      </p>
    </div>
  );
}

该组件会将格式化后的数字直接渲染到 DOM 中。对于 en-US,显示为 “1,500,000” 和 “23%”;对于 de-DE,显示为 “1.500.000” 和 “23 %”。

4. 从 loader 数据格式化数字

在 React Router 7 中,路由组件可通过 useLoaderData hook 获取 loader 数据。结合数字格式化,可以展示服务器提供的数值。

import { useLoaderData } from "react-router";
import { FormattedNumber } from "react-intl";

export async function loader() {
  return {
    revenue: 45678.9,
    units: 12500,
  };
}

export default function Dashboard() {
  const { revenue, units } = useLoaderData<typeof loader>();

  return (
    <div>
      <h1>Dashboard</h1>
      <p>
        Revenue:{" "}
        <FormattedNumber value={revenue} style="currency" currency="USD" />
      </p>
      <p>
        Units sold: <FormattedNumber value={units} />
      </p>
    </div>
  );
}

loader 提供原始数值数据,组件会根据用户的本地化设置进行格式化。这样可以将数据获取与展示逻辑解耦。