npm package: @ktx-crypto/format
Description: Crypto formatting utilities via composition
Latest version: 1.0.3

# KTX Format

相之前这种单函数 + options 对象的轮子：

```ts
export function formatCryptoPriceV2(
  price: number | string,
  options: {
    isProfit?: boolean;
    useLargeNumberAbbr?: boolean;
    prefix?: string;
    suffix?: string;
    forceZeroCount?: boolean;
  } = {}
): string {}
```

它的主要劣势是：

- 所有能力都堆在一个函数入口里，调用点会越来越重
- `options` 会不断膨胀，新加规则就要继续扩字段
- 很多格式规则只能靠布尔开关表达，组合关系不够直观
- 类型最终会收敛成一个越来越大的 options 接口，不如原子组合清晰

在 @ktx-crypto/format 设计中，鼓励**显式声明**每一项格式化规则，而不是依赖隐式的默认配置。
这意味着，你应该明确告诉 formatter 你想要应用什么样的格式规则，而不是依赖隐藏在实现内部的不透明“智能默认”。

**例子：**

```ts
// 不推荐之前的写法（隐式默认）:
const formatPrice = formatCryptoPriceV2(price); // 完全不知道最终结果

// 推荐（显式声明所需规则）:
const formatPrice = buildFormat( // 语义清晰，可读性更强
  withUnit('USDT'),
  withThousands(),
  fixPrecision(2),
)
```

这样做的好处是：

- 业务代码更具自解释性，减少“意外惊喜”
- 可读性更强，每个能力可定向查看和测试
- 不会因为将来的默认策略变化导致老代码语义漂移

**结论**：KTX Format 不会为格式规则设置“自动默认”，每一项都要求你手动、显式声明，明确哪一环节做了哪些格式处理。


这也是 KTX Format 改成 `buildFormat(...fns)(value)` 的原因：把“格式规则定义”和“值的消费时机”拆开，把每个能力拆成可组合、可复用、可测试的原子组合 formatter 更贴近业务语义，也更容易长期演进。

KTX Format 是一套 `data-last` 风格的数字格式化工具。你先组合一组格式化规则，再在真正使用时传入 value：

```ts
const formatter = buildFormat(...fns) // 先组合格式化规则(函数)
const output = formatter(value) // 真正使用时再传入待格式化值
```

它的目标很简单：

- 让格式化规则可复用
- 让多个格式化函数能自由组合
- 让最终输出不依赖外部书写顺序
- 保留原始值，便于后续扩展复杂逻辑

## 快速开始

```ts
import {
  buildFormat,
  fixPrecision,
  showExplicitSign,
  withSuffix,
  withThousands,
  withUnit,
} from '@ktx-crypto/format'

const formatPrice = buildFormat(
  withUnit('USDT'),
  showExplicitSign(),
  withThousands(),
  fixPrecision(2),
)

formatPrice(12002.1999)
// => "+12,002.20 USDT"
```

## 核心用法

### 1. `buildFormat` 只支持 `data-last`

```ts
buildFormat(...fns)(value)
```

### 2. formatter 可复用

```ts
const formatAmount = buildFormat(withThousands(), fixPrecision(2), withUnit('USDT'))

formatAmount(12002.1999)
// => "12,002.20 USDT"

formatAmount(88.1)
// => "88.10 USDT"
```

### 3. 命名建议

`buildFormat` 的返回值本质上不是一个临时函数，而是一个有明确业务职责的 formatter。

因此，变量名建议直接体现业务语义，而不是写成泛化命名：

推荐：

```ts
const formatPrice = buildFormat(...)
const formatAmount = buildFormat(...)
const formatPnL = buildFormat(...)
const formatFundingRate = buildFormat(...)
```

不推荐：

```ts
const formatter = buildFormat(...)
const fn = buildFormat(...)
const format = buildFormat(...)
```

这样做的好处是：

- 看到变量名就知道它处理的业务含义
- 在组件、hooks、store、utils 中更容易复用
- 同一个页面里存在多个 formatter 时不容易混淆

### 4. Vue 和 React 的使用方法

#### 在 Vue 项目中使用

**配合 computed 使用：**

```ts
import { computed } from 'vue'
import { buildFormat, withUnit } from '@ktx-crypto/format'

const formatAmount = buildFormat(withUnit('CNY'))

const formattedAmount = computed(() => formatAmount(amount.value))
```

#### 在 React 项目中使用

在 React 中完全等价用法。你可以直接在组件里引入 formatter 并在渲染时使用：

```tsx
import { buildFormat, fixPrecision, withThousands, withUnit } from '@ktx-crypto/format'

const formatPrice = buildFormat(withThousands(), fixPrecision(2), withUnit('USDT'))

export function Price({ value }: { value: number }) {
  return <span>{formatPrice(value)}</span>
}
```


> ⚡ 编写并组合 formatter 的思路，在 Vue 和 React 等框架之间可以无缝迁移，因为它是纯函数组合，不依赖任何框架 API。



## API 一览

### `buildFormat(...fns)`

把多个格式化函数组合成一个 formatter。

```ts
const formatter = buildFormat(...fns)
const output = formatter(value)
```

### `fixPrecision(precision)`

控制小数位数。

```ts
buildFormat(fixPrecision(2))(12.3456)
// => "12.35"
```

### `withThousands()`

为普通数字结果添加千分位。

```ts
buildFormat(withThousands(), fixPrecision(2))(12002.1999)
// => "12,002.20"
```

### `showExplicitSign()`

为正数显式添加 `+`，负数保持 `-`，`0` 不加 `+`。

```ts
buildFormat(showExplicitSign(), fixPrecision(2))(12.3)
// => "+12.30"
```

### `withPrefix(prefix)`

给结果最前面加前缀。

```ts
buildFormat(withPrefix('约 '), fixPrecision(2))(12.3)
// => "约 12.30"
```

### `withSuffix(suffix)`

给结果最后面加后缀，并且紧贴前面的数值内容，不自动补空格。

```ts
buildFormat(withSuffix('%'), fixPrecision(2))(12.3)
// => "12.30%"
```

如果同时和 `withUnit()` 一起使用，顺序会是“数值 -> suffix -> unit”：

```ts
buildFormat(fixPrecision(2), withSuffix('%'), withUnit('APR'))(12.3)
// => "12.30% APR"
```

### `withUnit(unit)`

把单位追加到结果后面，并始终自动补一个前置空格。

```ts
buildFormat(withUnit('USDT'), fixPrecision(2))(12.3)
// => "12.30 USDT"
```

例如：

```ts
buildFormat(formatAbbr(), withUnit('USDT'))(1234)
// => "1.23K USDT"

buildFormat(formatAbbr(true), withUnit('CNY'))(123456789)
// => "1.23亿 CNY"
```

### `withFallback(fallback)`

当输入是 `undefined`、`null`、空字符串或非法数字时，返回 fallback。

```ts
buildFormat(withFallback('--'))(undefined)
// => "--"
```

### `formatAbbr(isCN?)`

数字缩写。

```ts
buildFormat(formatAbbr())(1234567)
// => "1.23M"

buildFormat(formatAbbr(true))(123456789)
// => "1.23亿"
```

- `formatAbbr()` 或 `formatAbbr(false)`：国际化缩写，`K / M / B / T`
- `formatAbbr(true)`：中文缩写，`万 / 亿 / 万亿`

如果同时搭配 `fixPrecision()`，精度会应用在缩写后的值上：

```ts
buildFormat(formatAbbr(), fixPrecision(1))(1234567890)
// => "1.2B"
```

### `formatCompactZeros(threshold)`

把很长的前导小数零压缩成紧凑形式。

```ts
buildFormat(formatCompactZeros(5))(0.00000123)
// => "0.0{5}123"
```

含义是：当小数点后、首个非零数字前的连续 `0` 个数大于等于 `threshold` 时，使用压缩表示。

```ts
buildFormat(formatCompactZeros(5))(0.00000123)
// => "0.0{5}123"

buildFormat(formatCompactZeros(6))(0.00000123)
// => "0.00000123"
```

## 常见组合

### 金额展示

```ts
const formatPrice = buildFormat(
  showExplicitSign(),
  withThousands(),
  fixPrecision(2),
  withUnit('USDT'),
)

formatPrice(12002.1999)
// => "+12,002.20 USDT"
```

### 缩写金额

```ts
const formatCompactAmount = buildFormat(
  withPrefix('≈ '),
  showExplicitSign(),
  formatAbbr(),
  fixPrecision(1),
  withSuffix('%'),
  withUnit('USDT'),
)

formatCompactAmount(1234567)
// => "≈ +1.2M% USDT"
```

### 中文缩写金额

```ts
const formatCnAmount = buildFormat(
  showExplicitSign(),
  formatAbbr(true),
  fixPrecision(2),
  withUnit('CNY'),
)

formatCnAmount(123456789)
// => "+1.23亿 CNY"
```

### 百分比展示

```ts
const formatFundingRate = buildFormat(
  showExplicitSign(),
  fixPrecision(2),
  withSuffix('%'),
)

formatFundingRate(12.3)
// => "+12.30%"
```

### 极小值展示

```ts
const formatTinyPrice = buildFormat(
  withPrefix('价格 '),
  formatCompactZeros(4),
  showExplicitSign(),
  withUnit('BTC'),
)

formatTinyPrice(0.0000123)
// => "价格 +0.0{4}123 BTC"
```