currency-codes vs currency.js vs dinero.js vs numeral
前端货币处理与格式化库的技术选型指南
currency-codescurrency.jsdinero.jsnumeral类似的npm包:

前端货币处理与格式化库的技术选型指南

currency-codescurrency.jsdinero.jsnumeral 都是 JavaScript 生态中处理数字和货币的常用工具,但它们的定位截然不同。currency-codes 专注于提供全球货币代码、符号和精度的静态数据查询,不进行计算。currency.jsdinero.js 是专门的货币计算库,旨在解决浮点数精度问题,其中 dinero.js (v1) 采用不可变链式 API,而 currency.js 提供更轻量的可变或不可变操作。numeral 是一个通用的数字格式化库,用于将数字转换为人类可读的字符串(如添加逗号、百分比),但它不具备货币计算所需的精度安全特性。

npm下载趋势

3 年

GitHub Stars 排名

统计详情

npm包名称
下载量
Stars
大小
Issues
发布时间
License
currency-codes016587 kB242 年前MIT
currency.js03,378-635 年前MIT
dinero.js06,792858 kB296 个月前MIT
numeral09,702-3449 年前MIT

前端货币处理与格式化库的深度对比:从精度计算到数据查询

在前端开发中,处理金钱是一个高风险领域。JavaScript 原生的 Number 类型基于 IEEE 754 标准,无法精确表示某些小数(例如 0.1 + 0.2 !== 0.3)。为了解决这个问题,社区涌现了多种解决方案。我们将深入对比 currency-codescurrency.jsdinero.jsnumeral,看看它们各自如何解决不同层面的问题。

🎯 核心定位:它们到底做什么?

这四个库其实分为三类,混用它们会导致严重的架构问题。

currency-codes 是一个数据字典。它不包含任何计算逻辑,只告诉你 "USD" 代表美元,符号是 "$",通常保留 2 位小数。它就像一本字典,你查它获取信息,但它不会帮你算账。

currency.jsdinero.js计算引擎。它们的核心任务是用整数(Integer)在内部存储货币值(例如存 100 分而不是 1.00 元),从而彻底避开浮点数陷阱。它们提供加减乘除、分配和比较功能。

numeral 是一个格式化器。它的唯一工作是把数字变成好看的字符串。它不关心钱,只关心显示。如果你传给它是 0.1 + 0.2 的结果,它只会漂亮地显示 0.30000000000000004

🧮 精度计算:如何避免 0.1 + 0.2 !== 0.3

这是选择货币库最关键的理由。让我们看看各库如何处理基本的加法运算。

currency.js 允许你像写普通数学一样写代码,它在内部自动处理精度。

import currency from 'currency.js';

const price = currency(10.50);
const tax = currency(2.10);

// 直接相加,返回一个新的 currency 对象
const total = price.add(tax);
console.log(total.value); // 12.60 (精确)

dinero.js 采用更严谨的函数式风格,要求你明确指定货币单位和金额(通常以最小单位,如分为准)。

import dinero from 'dinero.js';

// 必须传入整数(分)和货币代码
const price = dinero({ amount: 1050, currency: 'USD' });
const tax = dinero({ amount: 210, currency: 'USD' });

// 链式调用,返回新对象(不可变)
const total = price.add(tax);
console.log(total.getAmount()); // 1260 (整数分)

numeral 没有计算能力。如果你试图用它处理计算,必须自己先算好(且容易出错)。

import numeral from 'numeral';

// 危险:原生浮点数运算
const rawTotal = 10.50 + 2.10; 
// 格式化仅用于显示
const formatted = numeral(rawTotal).format('$0,0.00'); 
console.log(formatted); // "$12.60" (看似正常,但若遇精度问题则失效)

currency-codes 完全不支持计算。尝试用它做算术会直接报错或毫无反应,因为它只是一个数据对象。

import { getCode } from 'currency-codes';

const data = getCode('USD');
// data.digits 是 2,data.symbol 是 '$'
// 没有 .add() 或 .multiply() 方法

🏗️ API 设计风格:链式不可变 vs 直观操作

API 的设计直接影响代码的可维护性和安全性。

dinero.js 坚持不可变性(Immutability)。每次操作都返回一个新对象,原对象保持不变。这在复杂的状态管理(如 Redux)中非常安全,能防止副作用,但代码写起来稍显冗长。

// dinero.js: 不可变链式
let myMoney = dinero({ amount: 1000, currency: 'USD' });
let updated = myMoney.add(dinero({ amount: 500, currency: 'USD' }));

// myMoney 仍然是 1000,updated 是 1500

currency.js 更加直观灵活。它虽然也返回新对象,但 API 设计得更像原生数字操作,甚至支持直接与普通数字运算(视配置而定),上手门槛更低。

// currency.js: 直观操作
let myMoney = currency(10.00);
let updated = myMoney.add(5.00); // 可以直接传数字

// 语法糖更多,适合快速开发

numeral面向过程的。你传入一个值,调用格式化方法,得到一个字符串。流程结束,没有对象状态需要维护。

// numeral: 过程式格式化
const result = numeral(1234567).format('0,0.00'); 
// 直接得到字符串 "1,234,567.00"

currency-codes纯函数查询。输入代码,输出数据对象。无状态,无副作用。

// currency-codes: 数据查询
const euroData = getCode('EUR');
// 直接获取属性 euroData.symbol

🌍 国际化与元数据:谁懂各国的钱?

处理全球业务时,你需要知道不同货币的符号、小数位数和代码。

currency-codes 是这方面的专家。它拥有最全面的 ISO 4217 标准数据。如果你需要动态获取 "JPY" 没有小数位,或 "BHD" 有三位小数,它是唯一选择。

import { getCode, getCodes } from 'currency-codes';

// 获取日元信息(0 位小数)
const jpy = getCode('JPY');
console.log(jpy.digits); // 0
console.log(jpy.symbol); // '¥'

// 遍历所有支持的国家
const allCodes = getCodes(); 

dinero.jscurrency.js 内部包含基本的货币信息以支持计算(如知道 USD 是 2 位小数),但它们不提供广泛的元数据查询接口。你通常需要将它们与 currency-codes 配合使用,或者手动配置。

// dinero.js 需要你自己传入正确的 amount (整数)
// 它不会告诉你 JPY 不需要小数,你必须自己知道并传入整数
const jpyAmount = dinero({ amount: 100, currency: 'JPY' }); 

numeral 的国际化依赖于加载特定的语言包(locales),主要用于翻译格式化模板(如逗号和点的位置),但它不懂货币本身的业务规则(如小数位数)。

import numeral from 'numeral';
import 'numeral/locales/locale-de-de';

numeral.locale('de-de');
console.log(numeral(1234.5).format('0,0.00')); // "1.234,50" (德国格式)

⚠️ 维护状态与风险提示

在选择库时,必须考虑其生命周期。

numeral 已经不再积极维护。它的 GitHub 仓库多年未更新,存在未修复的 Bug。在现代项目中,建议寻找替代品(如 Intl.NumberFormat 原生 API 或 dayjs 风格的轻量库),除非你只是用它做非常简单的静态格式化且不愿迁移。

dinero.js v1 版本已进入维护模式。团队正在全力开发 v2 版本(重写为 TypeScript,架构更优),但 v2 目前可能仍处于 Alpha/Beta 阶段。对于追求极致稳定的金融项目,v1 依然可用且可靠;对于新项目且不介意早期采用,可以关注 v2 的进展。

currency.jscurrency-codes 目前保持活跃维护状态,API 稳定,适合大多数生产环境。

📊 选型决策矩阵

特性currency-codescurrency.jsdinero.jsnumeral
核心用途货币元数据查询货币计算高精度货币计算数字格式化
计算精度N/A✅ 安全 (整数底层)✅ 安全 (整数底层)❌ 不安全 (原生 Number)
API 风格数据查询直观/轻量函数式/不可变链式格式化
国际化数据⭐⭐⭐⭐⭐ (最全)⭐⭐ (基础)⭐⭐ (基础)⭐⭐⭐ (格式本地化)
维护状态✅ 活跃✅ 活跃⚠️ v1 维护中/v2 开发❌ 已停滞
适用场景获取符号/位数快速开发/中小型应用金融级/大型复杂应用仅展示格式化

💡 最佳实践组合

在真实的企业级项目中,我们通常不会只选一个,而是组合使用

  1. 使用 currency-codes 获取当前用户所选货币的元数据(如小数位数)。
  2. 使用 dinero.js (或 currency.js) 进行所有的金额存储、传输和计算,确保数据绝对准确。
  3. 摒弃 numeral,改用浏览器原生的 Intl.NumberFormat 进行最后的格式化显示,因为它性能更好且无需额外依赖,或者在需要兼容旧浏览器时谨慎使用 numeral
// 推荐组合示例
import { getCode } from 'currency-codes';
import dinero from 'dinero.js';

// 1. 查询元数据
const currencyInfo = getCode('USD');

// 2. 安全计算
const amount = dinero({ amount: 1999, currency: 'USD' });
const total = amount.add(dinero({ amount: 500, currency: 'USD' }));

// 3. 原生格式化显示 (替代 numeral)
const display = new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD'
}).format(total.getAmount() / Math.pow(10, currencyInfo.digits));

console.log(display); // "$24.99"

🏁 总结

不要试图用一个库解决所有问题。如果你的需求是算得准,请在 dinero.jscurrency.js 之间选择;如果需要查数据,请用 currency-codes;如果只是为了好看,请优先考虑原生 API 而非 numeral。理解它们的边界,才能构建出稳健的金融前端架构。

如何选择: currency-codes vs currency.js vs dinero.js vs numeral

  • currency-codes:

    当你只需要查询货币元数据(如代码 'USD' 对应的符号 '$' 或小数位数)而不涉及任何数学运算时,选择 currency-codes。它是一个纯数据查找表,体积小巧,适合作为其他货币库的补充数据源,用于国际化展示或表单验证。

  • currency.js:

    如果你需要一个轻量级、易于上手的库来处理货币加减乘除,且希望 API 简单直观(支持直接数字运算),currency.js 是不错的选择。它适合中小型项目或快速原型开发,特别是在对不可变性要求不极端严格,但必须避免浮点数错误的场景。

  • dinero.js:

    在构建对财务准确性要求极高的大型应用(如银行系统、电商平台结算)时,首选 dinero.js。它的不可变链式 API 能有效防止状态意外修改带来的 Bug,且类型定义完善。注意:目前 v1 版本维护模式,新项目可评估其继任者 Dinero.js v2(alpha 阶段),若需稳定生产环境则 v1 依然可靠。

  • numeral:

    仅当你需要将已经计算好的数字(无论是普通数字还是货币金额)格式化为特定显示的字符串(如 '1,000.00' 或 '50%')时,才使用 numeral。切勿用它进行任何货币计算,因为它基于 JavaScript 原生 Number 类型,无法避免精度丢失问题。

currency-codes的README

currency-codes

A node.js module to list and work on currency codes based on the ISO 4217 standard.

npm install currency-codes

code('EUR')

var cc = require('currency-codes');
console.log(cc.code('EUR'));

/*
{
	code: 'EUR',
	number: 978,
	digits: 2,
	currency: 'Euro',
	countries: [
		'andorra', 'austria', 'belgium', 'cyprus', 'estonia', 'finland',
		'france', 'germany', 'greece', 'ireland', 'italy', 'kosovo',
		'luxembourg', 'malta', 'monaco', 'montenegro', 'netherlands',
		'portugal', 'san marino', 'slovakia', 'slovenia', 'spain',
		'vatican city' ]
}
*/

number(967)

var cc = require('currency-codes');
console.log(cc.number(967));

/*
{
	code: 'ZMW',
	number: 967,
	digits: 2,
	currency: 'Zambian kwacha',
	countries: [ 'zambia' ] }
*/

country('colombia')

var cc = require('currency-codes');
console.log(cc.country('colombia'));

/*
[
	{
		code: 'COP',
		number: 170,
		digits: 2,
		currency: 'Colombian peso',
		countries: [ 'colombia' ]
	}, {
		code: 'COU',
		number: 970,
		digits: 2,
		currency: 'Unidad de Valor Real',
		countries: [ 'colombia' ]
	}
]
*/

codes()

var cc = require('currency-codes');
console.log(cc.codes());

/*
[
	'AED',
	'AFN',
	...
	'ZAR',
	'ZMW'
]
*/

numbers()

var cc = require('currency-codes');
console.log(cc.numbers());

/*
[
	'784',
	'971',
	...
	'710',
	'967'
]
*/

countries()

var cc = require('currency-codes');
console.log(cc.countries());

/*
[
	'united arab emirates',
	'afghanistan',
	...
]
*/

data

var data = require('currency-codes/data');
console.log(data);

/*
[{
	code: 'AED',
	number: '784',
	digits: 2,
	currency: 'United Arab Emirates dirham',
	countries: ['united arab emirates']
}, {
	code: 'AFN',
	number: '971',
	digits: 2,
	currency: 'Afghan afghani',
	countries: ['afghanistan']
}, {
	...
*/

publishDate

var cc = require('currency-codes');

console.log(cc.publishDate);

/*
2024-06-25
*/

Updating the data

Fetch the latest copy of ISO-4217 from the maintainer and update this library's currency data file.

$ npm run iso

> currency-codes@2.1.0 iso
> npm run iso:fetch-xml && npm run iso:ingest-xml


> currency-codes@2.1.0 iso:fetch-xml
> node scripts/fetch-iso-4217-xml.js

Downloaded https://www.six-group.com/dam/download/financial-information/data-center/iso-currrency/lists/list-one.xml to iso-4217-list-one.xml

> currency-codes@2.1.0 iso:ingest-xml
> node scripts/ingest-iso-4217-xml.js

Ingested iso-4217-list-one.xml into data.js
Wrote publish date to iso-4217-publish-date.js

Note: You may have to manually tweak the capitalization of some country's names.

License

MIT