如何在 React Router v7 中格式化货币金额

以货币符号和分隔符显示价格

问题

在 Web 应用中显示价格时,需要同时处理两个相关的本地化问题:所用货币类型以及呈现货币金额的地区规范。例如,1200.50 美元在美国用户界面上显示为 "$1,200.50",但在法国则显示为 "1 200,50 $US"。货币符号的位置、小数点分隔符、千位分组和空格等格式都会因地区而异。如果不遵循这些规范,用户可能会误读金额,甚至怀疑价格的准确性,从而影响对应用的信任。

当应用服务于多个地区或允许用户切换不同货币时,这一挑战会更加复杂。硬编码格式字符串或手动放置货币符号会导致代码脆弱,一旦新增地区或货币就容易出错。没有系统化的方法,应用内价格显示的一致性和正确性将难以维护,容易出现错误。

解决方案

在格式化货币金额时,应同时指定目标货币代码和用户当前的本地化设置。这样可以将复杂的货币显示规则交由浏览器的国际化 API 处理,这些 API 已经内置了数百种地区与货币组合的格式规范。

只需将数值、货币代码和当前本地化信息传递给格式化函数,系统就会自动应用正确的符号、分隔符和排版方式。无论显示哪种货币或用户身处哪个地区,价格都能以用户熟悉的格式呈现,提升易用性和信任感。

步骤

1. 创建可复用的货币格式化组件

构建一个组件,使用 react-intl 的 FormattedNumber,并通过 stylecurrency 属性来格式化货币数值。

import { FormattedNumber } from "react-intl";

interface PriceProps {
  amount: number;
  currency: string;
}

export function Price({ amount, currency }: PriceProps) {
  return (
    <FormattedNumber value={amount} style="currency" currency={currency} />
  );
}

FormattedNumber 组件使用 formatNumber API,并接收 Intl.NumberFormatOptionsstyle="currency" 选项用于让格式化器包含货币符号,currency 属性指定要显示的货币类型。

2. 在路由组件中使用 Price 组件

在任意 React Router 路由组件中,通过传递数值和货币代码来渲染价格。

import { Price } from "~/components/Price";

export default function ProductPage() {
  const product = {
    name: "Wireless Headphones",
    price: 129.99,
    currency: "USD",
  };

  return (
    <div>
      <h1>{product.name}</h1>
      <p>
        <Price amount={product.price} currency={product.currency} />
      </p>
    </div>
  );
}

Price 组件会根据 IntlProvider 配置的本地化信息自动格式化金额。对于美国地区,会渲染为 "$129.99";对于使用相同 USD 货币的德国地区,则渲染为 "129,99 $"。

3. 按需以命令式方式格式化货币

如果需要直接获取格式化后的字符串,可以使用 useIntl hook 来访问 formatNumber 方法。

import { useIntl } from "react-intl";

export default function CheckoutSummary() {
  const intl = useIntl();
  const subtotal = 89.99;
  const tax = 7.2;
  const total = subtotal + tax;

  const formattedTotal = intl.formatNumber(total, {
    style: "currency",
    currency: "EUR",
  });

  return (
    <div>
      <h2>Order Summary</h2>
      <dl>
        <dt>Subtotal</dt>
        <dd>
          {intl.formatNumber(subtotal, {
            style: "currency",
            currency: "EUR",
          })}
        </dd>
        <dt>Tax</dt>
        <dd>
          {intl.formatNumber(tax, {
            style: "currency",
            currency: "EUR",
          })}
        </dd>
        <dt>Total</dt>
        <dd aria-label={`Total: ${formattedTotal}`}>
          <strong>{formattedTotal}</strong>
        </dd>
      </dl>
    </div>
  );
}

formatNumber 方法接收一个数值和包含 FormatNumberOptions 的选项对象。当你需要将格式化字符串用于属性、日志或非 JSX 场景时,这非常有用。

4. 控制整数货币的小数精度

部分货币没有小数单位。请根据货币习惯调整小数位数。

import { FormattedNumber } from "react-intl";

interface PriceProps {
  amount: number;
  currency: string;
}

export function Price({ amount, currency }: PriceProps) {
  const isWholeNumberCurrency = currency === "JPY" || currency === "KRW";

  return (
    <FormattedNumber
      value={amount}
      style="currency"
      currency={currency}
      minimumFractionDigits={isWholeNumberCurrency ? 0 : 2}
      maximumFractionDigits={isWholeNumberCurrency ? 0 : 2}
    />
  );
}

日元没有小数单位,因此金额显示时不带小数点。minimumFractionDigitsmaximumFractionDigits 选项可在需要时覆盖默认的小数行为。