
HarmonyKit | 鸿蒙新特性实践进制转换器 2/8/10/16 四进制通用互转引言进制转换是计算机科学的基础运算之一。虽然操作系统自带的计算器都提供了进制转换功能但在移动端开发调试场景中需要快速在二进制、八进制、十进制和十六进制之间切换查看同一个数值专用的进制转换器比通用计算器更加直接高效。HarmonyKit 的进制转换工具基于 JavaScript 原生的parseInt和toString方法配合 ArkTS 严格的类型系统实现了简洁而健壮的四进制互转功能。parseInt 与 toStringJavaScript 的进制转换原语在讨论 UI 实现之前有必要深入理解 JavaScript 为进制转换提供的两个核心 API。parseInt(string, radix)将指定进制radix的字符串解析为十进制整数其中 radix 的取值范围是 2 到 36。number.toString(radix)则相反将数字转换为指定进制的字符串表示。这两个方法构成了 HarmonyKit 进制转换工具的算法核心但实际上整个工具只用了两行核心逻辑letvalparseInt(this.input.trim(),this.fromRadix);this.results[val.toString(2),val.toString(8),val.toString(10),val.toString(16).toUpperCase()];第一行将用户输入按选定的源进制解析为十进制整数第二行将该整数分别转换为四种进制的字符串表示。注意toString(16)后调用toUpperCase()——JavaScript 的toString(16)默认输出小写字母a-f而十六进制惯例使用大写字母因此手动转为大写。二进制和八进制只输出数字不需要大小写处理。这种先统一到十进制再分发到各进制的模式是进制转换的通用解法。十进制作为人类最熟悉的中间表示逻辑清晰且不易出错。如果直接实现二进制到十六进制的转换算法需要处理 4 位一组的映射关系和补齐逻辑复杂度和出错概率都远高于这种中间表示法。接口类型RadixOption 与 ResultRowArkTS 严格模式下要求 ForEach 的循环变量必须有明确的类型标注。进制转换工具定义了两个接口类型来描述数据结构interfaceRadixOption{label:string;radix:number;}interfaceResultRow{label:string;index:number;}RadixOption用于描述四个进制选择按钮。label是按钮上显示的文本如 “10(Dec)”方便用户快速识别radix是对应的基数直接传给parseInt。将这两个信息封装在接口中而非使用两个独立数组保证了标签与基数的对应关系不会因为数组索引错位而出错。ResultRow用于描述四个结果行。label是行的中文名称如 “十六进制 (HEX)”index对应this.results数组中的索引位置十六进制在数组中的索引是 3。之所以不在接口中直接存储结果值是因为结果值会随用户操作动态变化而标签和索引是静态的。将可变数据和不可变元数据分离是响应式 UI 设计的重要原则。数据初始化与静态配置页面中定义了两个私有数组作为静态配置数据privateradixOptions:RadixOption[][{label:2(Bin),radix:2},{label:8(Oct),radix:8},{label:10(Dec),radix:10},{label:16(Hex),radix:16}];privateresultLabels:ResultRow[][{label:二进制 (BIN),index:0},{label:八进制 (OCT),index:1},{label:十进制 (DEC),index:2},{label:十六进制 (HEX),index:3}];这两个数组使用private而非State修饰——它们的内容在组件生命周期内不会改变不需要触发 UI 重渲染。鸿蒙的 ArkUI 框架中State修饰的变量变化会触发build()方法重新执行而普通类成员变量不会。正确地区分哪些数据需要响应式追踪哪些不需要可以避免不必要的 UI 重绘提升性能。初始化时aboutToAppear()调用convert()方法对默认值255十进制执行转换使得页面加载后立即显示四种进制的转换结果。255是一个特别好的默认值因为它在二进制中是全 111111111在十六进制中是 FF对于开发者来说是熟悉的边界值。源进制选择器的 UI 实现四个源进制选择按钮使用ForEach循环渲染Row(){Text(源进制).fontSize(12).fontColor(#666);Blank();ForEach(this.radixOptions,(opt:RadixOption){Button(opt.label).fontSize(10).height(26).backgroundColor(this.fromRadixopt.radix?#007aff:#f0f0f0).fontColor(this.fromRadixopt.radix?#ffffff:#666).borderRadius(6).padding({left:8,right:8}).margin({left:4}).onClick((){this.fromRadixopt.radix;});});}按钮的当前选中状态通过backgroundColor和fontColor的条件表达式体现当this.fromRadix opt.radix时使用蓝色主题色否则使用灰色。这里使用了 ArkTS 的三元表达式简洁但要注意三元表达式的两个分支返回类型必须一致。由于#007aff和#f0f0f0都是字符串类型编译通过。onClick中只修改this.fromRadix不自动触发转换。这是刻意为之——用户可能在切换源进制后还需要修改输入值自动转换会产生中间状态的无效结果。用户需要手动点击转换按钮来确认操作这符合显式操作的交互原则也避免了不必要的计算。输入框与转换按钮十进制默认值255预填充在输入框中TextInput({text:this.input,placeholder:输入数值...}).fontSize(16).fontFamily(monospace).backgroundColor(#ffffff).borderRadius(8).padding(12).margin({left:16,right:16,top:8}).onChange((v:string){this.inputv;});fontFamily(monospace)用于等宽显示——数字在等宽字体下排列整齐便于用户检查输入。placeholder给出了输入提示但不过于具体不写输入十进制数值因为源进制可以通过按钮切换为 2、8、16。转换按钮触发convert()方法convert(){letvalparseInt(this.input.trim(),this.fromRadix);if(isNaN(val)){this.results[无效输入,无效输入,无效输入,无效输入];return;}this.results[val.toString(2),val.toString(8),val.toString(10),val.toString(16).toUpperCase()];}isNaN(val)判断覆盖了多种无效输入情况空字符串、非数字字符如 “xyz”、在当前进制下不合法的字符如十六进制模式下的输入 “10” 是合法的但输入 “1G” 就不合法因为 G 超出了 0-9/A-F 的范围、以及值为 NaN 的特殊情况。注意parseInt对不合法的输入返回NaN而NaN有一个著名的 JS 怪癖typeof NaN number但NaN ! NaN所以必须用isNaN()函数而非来判断。当输入无效时所有四个结果行都显示无效输入而非保持上一次的旧结果。这避免了让用户误以为上一次的转换结果对应的是当前无效输入的混淆。结果列表的渲染结果区域通过ForEach循环渲染四个结果行ForEach(this.resultLabels,(row:ResultRow){Row(){Text(row.label).fontSize(11).fontColor(#999).width(90);Text(this.results[row.index]||-).fontSize(14).fontFamily(monospace).fontColor(#1a1a1a).layoutWeight(1);if(this.results[row.index]){CopyButton({text:this.results[row.index]});}}.width(100%).padding({left:16,right:16,top:8,bottom:8}).backgroundColor(#ffffff).border({width:{bottom:0.5},color:#f0f0f0});});每行包含三个部分左侧的进制名称标签固定宽度 90vp、中间的结果值弹性宽度layoutWeight(1)、右侧的复制按钮条件显示。底部分隔线使用border的bottom属性模拟 0.5px 的细线——在 Retina 屏幕上0.5vp 的边框渲染为 1 个物理像素比 1vp 的粗线更具现代设计感。复制按钮的显示条件是if (this.results[row.index])——只有当结果值非空时无论是有效结果还是无效输入文本才显示。在初始状态或输入为空时result 为空字符串falsy复制按钮隐藏。这避免了允许用户复制空内容或无意义内容的尴尬。当 result 为空字符串时this.results[row.index] || -表达式会显示-占位符。||运算符在 ArkTS 中的行为与 JavaScript 一致左侧为 falsy空字符串、0、null、undefined、NaN时返回右侧值。所以要特别注意——如果转换结果恰好是0||会错误地显示-。但在进制转换的场景中数字 0 的字符串表示是0非空字符串truthy所以不存在这个问题。错误输入的覆盖范围parseInt的行为有一些反直觉的边缘情况值得注意。例如parseInt(0x10, 16)返回 16——即使指定了 radix 为 16parseInt 仍然会识别前缀0x并自动忽略它。而parseInt(010, 8)在严格模式下可能不识别前导零。为了保持一致性RadixConverter 的输入框约定用户输入不含前缀如不输入0x或0b源进制由按钮选择的 radix 参数决定。另一个细节是parseInt对超出数字范围的字符采用尽可能解析的策略。例如parseInt(10abc, 16)返回0x10a 266——它解析了10a部分并忽略了bc之后的字符。这可能导致用户误以为输入被完整处理。HarmonyKit 的当前实现利用了这种宽容行为对大多数正常输入如纯数字加合法字母解析正确而对完全无效的输入如纯字母xyz返回 NaN 并显示错误。如果要严格验证可以在parseInt之后检查原始字符串与val.toString(fromRadix)是否一致忽略大小写。但这种严格模式可能拒绝了合理的输入如十六进制的小写字母需要根据使用场景权衡。当前工具选择的是便利优先策略。数据流动的全链路追踪以用户操作源进制选择 16输入 FF点击转换为例追踪数据流动用户点击 “16(Hex)” 按钮 →this.fromRadix 16用户在 TextInput 中输入 “FF” →this.input FF用户点击转换按钮 →convert()被调用parseInt(FF, 16)→ 返回 255十进制整数val.toString(2)→11111111val.toString(8)→377val.toString(10)→255val.toString(16).toUpperCase()→FFthis.results [11111111, 377, 255, FF]State results的变化触发build()重新执行ForEach 循环更新四个结果行这个链路的每一步都是同步操作。整个转换过程在单帧内完成通常 1ms用户感受不到任何延迟。这也是为什么进制转换工具不需要 loading 状态或异步处理。关于大数精度JavaScript 的number类型基于 IEEE 754 双精度浮点数整数安全范围是 ±2^53-1约 ±9007199254740991。超出此范围的整数会丢失精度。例如parseInt(9999999999999999, 10).toString(16)可能不会返回期望的值。HarmonyKit 的进制转换工具没有对此做特殊处理——这是 JavaScript 语言本身的限制。对于绝大多数开发调试场景如颜色值 0-255、端口号 0-65535、内存地址 0-0xFFFFFFFF双精度浮点数的整数精度完全足够。但如果需要处理 64 位整数的进制转换如区块链哈希、大文件校验值则需使用BigInt类型ES2020 新增通过BigInt(0x hexString)和bigint.toString(radix)来处理。与计算器类工具的共性RadixConverter 与 ColorConverter、TimestampConverter 同属计算类工具。它们共享以下模式静态工具方法核心计算逻辑封装在 utils 类的静态方法中虽然 RadixConverter 的convert方法直接内联因为逻辑足够简单aboutToAppear 初始化页面加载时立即执行一次计算展示默认值的结果State 驱动 UI计算结果存储在 State 变量中变化时自动触发 UI 更新错误提示模式输入无效时显示用户友好的文本而非抛出异常RadixConverter 的特殊之处在于它不需要单独的工具类——parseInt和toString都是 JavaScript 内置方法不存在需要封装的复杂算法。这引出了一个有趣的设计哲学问题什么时候该把逻辑抽到 utils 中答案是当逻辑需要多页面复用时或当逻辑足够复杂需要单独测试时。对于两行核心代码的进制转换内联在页面中反而是最清晰的做法。UI 布局的设计考量进制转换工具的整体布局遵循 HarmonyKit 的统一模板顶部导航栏返回按钮 工具标题 可滚动内容区。但与编解码类工具使用输入→操作按钮→输出的纵向管道不同进制转换使用了源进制选择→输入→转换按钮→分隔线→结果列表的横向纵向混合布局。源进制选择器使用了Blank()组件在标签和按钮组之间创建弹性间隔使按钮组右对齐。这与layoutWeight的弹性布局有微妙区别Blank在 Row 中占据剩余空间并将两侧元素推开相当于 flexbox 中的justify-content: space-between而layoutWeight是按比例分配空间。在选项固定的场景下4 个等宽按钮两者效果类似但Blank的语义更清晰——“标签靠左按钮靠右”。小结进制转换工具是 HarmonyKit 中最简单的工具之一——核心逻辑仅两行代码。但正是这种极简功能最能体现在交互上不偷懒的设计态度。四个源进制按钮的选中态切换、结果行的列表式展示、无效输入的统一处理、条件显示的复制按钮——每一个细节都在降低用户的操作成本。技术层面该工具是 JavaScript/ArkTS 原生能力在鸿蒙应用中的直接运用。parseInt和toString是每个前端开发者都熟悉的方法但将它们与 ArkUI 的State、ForEach、条件渲染结合就构建出了一个有实际价值的开发者工具。这启示我们好的工具不一定需要高深的技术精确地解决一个小问题本身就足够有价值。项目仓库https://atomgit.com/VON-/harmony-kit