专栏:HarmonyOS 三方库适配实战(9/30)
核心问题:怎样把“运行时随意长属性”的 TypeScript 数据改成可解释、可验证的 ArkTS 契约。
摘要
很多 TypeScript 工具库用any、索引签名、对象展开或运行时属性写入处理选项。迁移到 ArkTS 时,真正要修的不是一个关键字,而是数据契约:允许哪些字段、默认值在哪里产生、未知字段怎样处理、错误如何返回。本文用文本规范化选项演示从动态字典到显式 class,并用结果对象表达值和变化状态。代码已进入教学 HAR 并通过 API 22 编译,但尚未做设备运行。
先写清原始行为,不要先改类型
假设上游接受一个动态选项对象:
functionnormalize(input:string,options:Record<string,unknown>):string{letvalue=inputif(options['trim']===true){value=value.trim()}if(options['lowercase']===true){value=value.toLowerCase()}returnvalue}这段代码隐藏了四个问题:字段集合不明确、未知字段被静默忽略、值类型只能在运行时判断、调用方无法从签名知道默认值。官方从 TypeScript 到 ArkTS 的适配规则强调静态类型、固定对象布局,并给出用明确类或接口替代动态写法的方向。
改造前先记录行为:trim、collapseWhitespace、lowercase的顺序是什么;缺字段和false是否等价;非布尔值是拒绝还是忽略;空字符串是否允许。没有这一步,类型更严格也可能把语义改错。
第一步:把配置变成显式 class
以下代码与本地教学工程一致,放在库模块的src/main/ets/model/NormalizeOptions.ets:
exportclassNormalizeOptions{trim:booleancollapseWhitespace:booleanlowercase:booleanconstructor(trim:boolean,collapseWhitespace:boolean,lowercase:boolean){this.trim=trimthis.collapseWhitespace=collapseWhitespacethis.lowercase=lowercase}}显式构造器迫使调用方决定三个选项。若业务确实需要默认值,可以新增语义明确的工厂方法,例如NormalizeOptions.defaults(),而不是让undefined在多个函数中各自解释。
第二步:让返回值也成为稳定契约
只返回字符串时,调用方不知道是否发生变化。教学库用结果对象同时返回值和状态:
exportclassNormalizationResult{value:stringchanged:booleanconstructor(value:string,changed:boolean){this.value=valuethis.changed=changed}}changed的语义必须固定为“最终字符串是否与原输入不同”,而不是“是否启用了某个选项”。否则启用trim但输入本来没有空白时,会出现误导。
第三步:把处理顺序写进实现
代码放在src/main/ets/core/TextNormalizer.ets:
import{NormalizationResult,NormalizeOptions}from'../model/NormalizeOptions'functioncollapseSpaces(input:string):string{returninput.replace(/\s+/g,' ')}exportfunctionnormalizeText(input:string,options:NormalizeOptions):NormalizationResult{letvalue:string=inputif(options.trim){value=value.trim()}if(options.collapseWhitespace){value=collapseSpaces(value)}if(options.lowercase){value=value.toLowerCase()}returnnewNormalizationResult(value,value!==input)}顺序现在可读:先裁剪两端,再折叠空白,最后小写。顺序属于 API 行为,未来修改必须经过回归测试,而不是当作内部细节随意调整。
第四步:在边界层处理不可信输入
公开 ArkTS 调用已经要求NormalizeOptions,但配置文件、网络 JSON 或 JS 互操作仍可能提供不可信数据。不要把动态数据直接传进核心函数;在边界层逐字段读取、校验,再构造明确模型。
exportfunctioncreateDefaultOptions():NormalizeOptions{returnnewNormalizeOptions(true,true,false)}若需要解析 JSON,应单独定义parseOptions并明确:未知字段是报错还是忽略、缺失字段取什么默认值、类型错误如何表示。本文不伪造一个未编译的通用 JSON 解析器,因为不同数据来源的接口与错误模型不同。
图 1:数据边界与核心模型(示意图)。动态数据只停留在边界层,核心函数接收固定类型。
正常、边界和错误输入怎么验
| 用例 | 输入 | 选项 | 期望 |
|---|---|---|---|
| normal-01 | " HarmonyOS Library " | true,true,false | HarmonyOS Library,changed=true |
| normal-02 | " HarmonyOS Library " | true,true,true | harmonyos library,changed=true |
| boundary-01 | "HarmonyOS" | true,true,false | 值不变,changed=false |
| boundary-02 | "" | true,true,false | 空字符串,changed=false |
| error-01 | 非字符串来源 | 在边界层拒绝 | 不进入核心函数 |
当前构建证明这些类型、构造器和调用路径被目标 SDK 接受;由于没有运行测试,表中的结果仍是契约期望,而不是设备实测输出。第 16 篇会把向量迁移成 ArkTS 回归集。
为什么不用“万能 Map”逃避建模
Map<string, Object>看起来静态,实际上仍把字段名、值类型和默认语义留给运行时。只有当键集合本来就开放,例如用户自定义标签,Map 才是合理模型。固定选项应优先使用 class 或明确接口。
兼容性取舍:严格模型可能暴露上游的宽松行为
显式构造器会让以前被静默忽略的未知字段无法直接传入。这可能是改进,也可能破坏既有调用方。适配记录应说明:
- 是否保留旧字段别名;
- 未知字段是否拒绝;
- 默认值是否与上游一致;
- 错误是抛异常还是返回结果对象。
如果无法证明一致,不要写“无损迁移”,应写“公共契约已收窄,待迁移上游测试确认”。
图 2:改造前后契约对比(示意图)。类型收窄后仍需上游测试确认行为兼容。
本篇产物与下一篇门槛
本篇产物是NormalizeOptions、NormalizationResult和normalizeText三个明确公共类型/符号。进入第 10 篇前,必须能从单一入口导入它们,并确认内部帮助函数没有被暴露。
官方来源
- 从 TypeScript 到 ArkTS 的适配规则:用于静态类型、固定对象布局、对象字面量和结构类型的迁移依据。访问日期:2026-10-18。
- ArkTS 迁移背景:用于解释将部分运行时错误提前到开发期检查的目标。访问日期:2026-10-18。
验证声明
本文展示的NormalizeOptions、NormalizationResult和normalizeText已写入本地教学 HAR;2026-10-18 使用 HarmonyOS 6.0.2 Release(API 22)完成assembleHar,消费方通过包入口导入这些符号并完成assembleHap,两个任务退出码均为 0。尚未执行 ArkTS 单元测试、模拟器或真机,因此示例的行为表是待运行的契约,不冒充设备结果。