如何在 React Router v7 中格式化货币金额
使用货币符号和分隔符显示价格
问题
在 Web 应用程序中显示价格需要处理两个相关的本地化问题:所使用的货币和呈现货币值的区域惯例。1200.50 美元的价格在美国用户看来是 "$1,200.50",但在法国则显示为 "1 200,50 $US"。货币符号的位置、小数点分隔符、千位分隔符和间距都因地区而异。如果不遵循这些惯例,用户可能会误读金额或质疑价格的正确性,从而削弱对应用程序的信任。
当一个应用程序服务于多个地区或允许用户以不同货币查看价格时,这一挑战变得更加复杂。硬编码格式字符串或手动放置符号会导致代码脆弱,当添加新地区或货币时容易出错。如果没有系统化的方法,在整个应用程序中保持一致且正确的价格显示将变得容易出错。
解决方案
使用目标货币代码和用户的活动区域来格式化货币值。这种方法将货币呈现的复杂规则委托给浏览器的国际化 API,这些 API 已经编码了数百种地区和货币组合的格式化惯例。
通过将数值金额、货币代码和当前区域传递给格式化函数,系统会自动应用正确的符号、分隔符和布局。这确保了无论显示哪种货币或用户位于哪个地区,价格始终以用户熟悉的格式显示。
步骤
1. 创建一个可复用的货币格式化组件
构建一个使用 react-intl 的 FormattedNumber 组件,并通过 style 和 currency 属性来格式化货币值。
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.NumberFormatOptions。style="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 钩子访问 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>订单摘要</h2>
<dl>
<dt>小计</dt>
<dd>
{intl.formatNumber(subtotal, {
style: "currency",
currency: "EUR",
})}
</dd>
<dt>税费</dt>
<dd>
{intl.formatNumber(tax, {
style: "currency",
currency: "EUR",
})}
</dd>
<dt>总计</dt>
<dd aria-label={`总计: ${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}
/>
);
}
例如,日元不使用小数单位,因此金额会以无小数的形式显示。minimumFractionDigits 和 maximumFractionDigits 选项在必要时会覆盖默认的小数行为。