如何在 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 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}
/>
);
}
日元没有小数单位,因此金额显示时不带小数点。minimumFractionDigits 和 maximumFractionDigits 选项可在需要时覆盖默认的小数行为。