打造通用单位转换工具:从需求到实现的完整指南
在日常开发中,我们经常会遇到单位转换的需求。比如能源数据中从 Wh 到 kWh 再到 MWh 的转换,存储容量中从 B 到 KB、MB 的切换,还有金额从元到万元、亿元的换算。如果每次都重复编写转换逻辑,不仅效率低下,还容易出现 bug。今天就来分享一个基于 TypeScript 实现的通用单位转换工具,它能灵活适配不同类型的单位转换需求,还能保证类型安全,让你的代码更优雅。
一、需求分析:我们需要什么样的单位转换工具?
在开始编码前,先明确工具需要满足的核心需求:
- 多类型适配:能支持不同类型的单位转换,比如能源(kwh)、功率(kw)、金额(price)、存储(bit)等,且每种类型的单位体系可自定义。
- 自动进位:根据设定的阈值(limit)自动完成单位进位,比如当数值超过 1000 时,从 Wh 自动转为 kWh。
- 批量统一转换:支持对数组数据进行批量转换,确保同一批数据使用统一的单位体系。
- 类型安全:基于 TypeScript 实现,提供完整的类型定义,避免使用 any 类型,在编译阶段就能发现潜在错误。
- 易用性:生成的转换方法命名直观,调用方式简单,开发者无需关注内部实现细节。
二、核心代码解析:从类型定义到功能实现
接下来我们逐段解析核心代码,看看这个工具是如何满足上述需求的。
1. 基础工具函数:首字母大写
首先定义一个简单的工具函数toUpperCase,用于后续生成方法名时将类型名的首字母大写,保证方法名的规范性(比如将bit转为uniformUnitsBitBy)。
TS
const toUpperCase = (data: string) => `${data.charAt(0).toUpperCase()}${data.slice(1)}`;
这个函数逻辑很简单:获取字符串的第一个字符并转为大写,再拼接剩余部分,比如输入bit会返回Bit。
2. 核心类型定义:确保类型安全
TypeScript 的核心优势在于类型系统,这里我们通过多个接口和泛型定义,为工具构建完整的类型约束。
(1)单位配置类型UnitConfig
定义单个类型的单位配置结构,包含两个关键属性:
- limit:单位进位的阈值,比如 1000(表示数值超过 1000 时进位)、1024(存储单位常用的进位阈值)。
- unit:该类型对应的单位数组,按从小到大的顺序排列,比如['wh', 'kWh', 'MWh', 'GWh']。
TS
type UnitConfig = {
limit: number;
unit: readonly string[]; // 使用readonly确保单位数组不可修改,避免意外篡改
};
(2)整体配置类型Config
使用Record类型定义整体配置,键为类型名(比如kwh、bit),值为对应的UnitConfig配置。这样就能灵活扩展不同类型的单位体系。
TS
type Config = Record<string, UnitConfig>;
(3)转换结果类型UnitConvertResult
定义单个数值转换后的返回结果,包含转换后的数值(size)和对应的单位(unit)。
TS
type UnitConvertResult = {
size: number;
unit: string;
};
(4)工具返回类型UnitConvertResultType
这是最核心的类型定义,通过泛型和映射类型,动态生成工具的返回类型。它包含两部分:
- 第一部分:为每种配置类型生成单独的转换方法,比如kwh类型会生成(value: number) => UnitConvertResult方法。
- 第二部分:为每种配置类型生成批量转换方法,方法名格式为uniformUnits${Capitalize<类型名>}By(比如uniformUnitsBitBy),支持传入数组和取值函数,批量处理数据。
TS
type UnitConvertResultType<T> = {
/** 单个数值单位转换 */
[K in keyof T]: (value: number) => UnitConvertResult
}
&
{
/** 批量数据统一单位转换 */
[K in keyof T as `uniformUnits${Capitalize<string & K>}By`]: <T>(values: T[], fn: (item: T) => number) => UnitConvertResult[]
}
这里使用了 TypeScript 的映射类型 ([K in keyof T])和模板字面量类型 (uniformUnits${Capitalize<string & K>}By
),实现了动态生成方法名和类型的功能,确保类型安全。
3. 核心函数实现:createUnitConvert
createUnitConvert是工具的入口函数,接收用户传入的配置config,返回包含转换方法的对象。它的实现分为三步:
(1)定义内部转换逻辑convert
首先在函数内部定义convert函数,负责单个数值的单位转换逻辑:
- 接收type(转换类型,比如bit)和value(待转换数值)。
- 从配置中获取该类型的limit和unit数组。
- 通过while循环实现自动进位:当数值大于等于limit且未达到最大单位时,持续将数值除以limit,并递增单位索引。
- 最后返回转换后的结果(size和unit)。
TS
const convert = <K extends keyof T>(type: K, value: number): UnitConvertResult => {
const { limit, unit } = config[type];
let size = value;
let unitIndex = 0;
// 自动进位逻辑:数值超过阈值且未到最大单位时,持续进位
while (size >= limit && unitIndex < unit.length - 1) {
size /= limit;
unitIndex++;
}
return {
size,
unit: unit[unitIndex]
};
};
(2)构建结果对象result
创建一个空对象result,用于存储生成的转换方法。然后遍历配置的所有键(类型名),为每个类型生成两个方法:
- 单个转换方法:直接将value传入convert函数,返回转换结果。
- 批量转换方法:接收values数组和fn取值函数,先通过fn从数组项中提取数值,再调用convert函数批量转换。
TS
const result: any = {};
// 遍历配置,为每个类型生成转换方法
(Object.keys(config) as (keyof T)[]).forEach(key => {
// 单个数值转换方法:key对应类型名,比如bit()
result[key] = (value: number) => convert(key, value);
// 批量转换方法:方法名格式为uniformUnits${首字母大写类型名}By,比如uniformUnitsBitBy()
result[`uniformUnits${toUpperCase(key as string)}By`] = <T>(values: T[], fn: (item: T) => number) =>
values.map(item => convert(key, fn(item)));
});
(3)返回结果并断言类型
最后将result断言为UnitConvertResultType类型,确保返回的方法与定义的类型一致,提供完整的类型提示。
TS
return result as UnitConvertResultType<T>;
三、使用示例:快速上手工具
工具实现完成后,使用方式非常简单。我们以能源、功率、金额、存储四种类型为例,演示如何使用:
1. 创建转换工具实例
首先调用createUnitConvert函数,传入不同类型的单位配置,生成unitConvert实例:
TS
const unitConvert = createUnitConvert({
kwh: { // 能源单位:wh -> kWh -> MWh -> GWh,阈值1000
limit: 1000,
unit: ['wh', 'kWh', 'MWh', 'GWh'],
},
kw: { // 功率单位:kW -> MW -> GW,阈值1000
limit: 1000,
unit: ['kW', 'MW', 'GW'],
},
price: { // 金额单位:元 -> 万元 -> 亿元,阈值10000
limit: 10000,
unit: ['元', '万元', '亿元'],
},
bit: { // 存储单位:B -> KB -> MB -> GB -> TB,阈值1024
limit: 1024,
unit: ['B', 'KB', 'MB', 'GB', 'TB'],
},
});
2. 调用转换方法
生成实例后,就可以直接调用对应的转换方法了。
(1)单个数值转换
调用unitConvert.类型名(数值)即可完成单个数值的转换。比如转换存储容量-100(注意:负数不会触发进位,因为size >= limit条件不满足): TS
TS
console.log(unitConvert.bit(-100));
// 输出:{ size: -100, unit: 'B' }
再比如转换金额25000元(阈值 10000,会进位到 "万元"):
TS
console.log(unitConvert.price(25000));
// 输出:{ size: 2.5, unit: '万元' }
(2)批量数据转换
调用unitConvert.uniformUnits类型名By(数组, 取值函数)即可批量转换数组数据。比如批量转换存储容量数组[1024, 2048, 3072]:
TS
const bitData = [1024, 2048, 3072];
console.log(unitConvert.uniformUnitsBitBy(bitData, item => item));
// 输出:
// [
// { size: 1, unit: 'KB' },
// { size: 2, unit: 'KB' },
// { size: 3, unit: 'KB' }
// ]
如果数组项是对象,也可以通过取值函数提取数值。比如转换金额对象数组:
TS
const priceData = [
{ id: 1, amount: 5000 },
{ id: 2, amount: 15000 },
{ id: 3, amount: 250000 }
];
console.log(unitConvert.uniformUnitsPriceBy(priceData, item => item.amount));
// 输出:
// [
// { size: 5000, unit: '元' },
// { size: 1.5, unit: '万元' },
// { size: 25, unit: '万元' }
// ]
四、工具优势:为什么推荐使用这个工具?
- 高度灵活:支持自定义单位体系和进位阈值,无论是 1000 进位的能源单位,还是 1024 进位的存储单位,都能轻松适配。
- 类型安全:基于 TypeScript 实现,提供完整的类型定义,调用方法时能获得精准的类型提示,避免传参错误。
- 易用性强:生成的方法命名直观,调用方式简单,开发者无需关注内部逻辑,开箱即用。
- 扩展性好:如果需要新增单位类型(比如长度、重量),只需在配置中添加对应的UnitConfig,工具会自动生成对应的转换方法,无需修改核心代码。
- 批量处理:支持数组批量转换,特别适合处理表格数据、图表数据等场景,确保同一批数据单位统一。
五、总结与扩展建议
本文实现的通用单位转换工具,通过 TypeScript 的类型系统和灵活的配置设计,解决了日常开发中单位转换的重复编码问题。它不仅能满足当前的需求,还能轻松应对未来新增的单位类型。
如果需要进一步扩展工具的功能,可以考虑以下方向:
- 支持自定义保留小数位数:在转换结果中添加precision参数,允许用户指定保留的小数位数。
- 支持反向转换:实现从大单位到小单位的转换,比如从 kWh 转回 Wh。
- 添加单位格式化功能:将size和unit拼接为字符串,比如1.5 万元,方便直接展示。
- 支持负数特殊处理:可配置负数是否触发进位,满足不同场景的需求。
希望这个工具能帮助你提升开发效率,让代码更优雅。如果有任何问题或优化建议,欢迎在评论区交流!