把业务链路迁到 ArkUI:难的不是页面复刻,状态与并发才是问题

0 评论 286 浏览 1 收藏 12 分钟

鸿蒙迁移不只是界面还原,更是业务状态与异步时序的重新定义。本文从状态管理、并发竞争到条件编译,剖析迁移中易被忽视的深层问题,帮助你避免“页面相同、行为不同”的陷阱,确保关键链路可靠运行。

把一个 Android 或 iOS 页面搬到鸿蒙,最容易产生的错觉是什么?

界面还原完成,迁移也就完成了。

是不是很自然?

页面最直观:按钮、列表、弹窗和加载动画都能被截图比对;但业务状态和异步时序却藏在交互背后,很难看见背后的问题。

页面可能已经长得一模一样,但用户返回再进入时数据丢了,连续点击后出现重复提交,较早发出的请求覆盖了较晚的选择,网络恢复后界面仍停在错误状态。真正拖垮迁移质量的,往往不是组件缺了一个圆角,而是旧端那些没有被写进设计稿的运行规则没有一起迁过来。

所以,迁移一条业务链路时,首先不该是建立页面清单,而是回答一个最基础的问题:这条链路在任何时刻,究竟允许处于什么状态?

一、先迁移行为,不要先迁移截图

我们设想一条常见的业务链路:用户选择本地文件、发起解析、修改识别结果、提交处理、然后查看任务状态(这里的场景只是评估框架,不代表某个真实项目)。

如果按页面拆任务,团队会得到四个页面:文件选择页、识别结果页、确认页、任务状态页。

每个页面都能单独开发,也都能单独验收。

但问题是:用户使用的是一条连续链路,而不是四张彼此无关的截图。

  • 解析尚未结束时,用户能否重新选择文件?
  • 后一次解析先完成,前一次迟到的结果应该怎样处理?
  • 提交按钮被连续点击两次,是前端禁用、业务层去重,还是服务端保证幂等?
  • 应用切到后台再回来,进行中的任务继续、取消,还是重新确认?

这些问题决定了链路是否可靠,却不会出现在静态页面中。

更有效的迁移起点,是把旧端行为写成一张状态表。至少要列出四类信息:当前业务状态、允许的用户动作、可能发生的异步事件、事件发生后的唯一结果。比如“待编辑”可以接受修改和提交,“提交中”只能等待或取消,“成功”和“失败”都应是明确的终态;如果业务允许重试,重试也应该从失败态走一条清楚的转换路径,而不是让页面随意改几个布尔值。

这样做看起来比照着设计稿写组件慢,实际上是在提前还债。

因为当状态没有统一定义时,一个页面里通常会同时出现 `isLoading`、`hasData`、`showError`、`canSubmit` 等多个开关。它们各自都合理,组合起来却可能产生“正在加载但又显示失败”“提交成功后按钮仍可点击”之类的非法状态。迁移不是把这些开关原样复制过去,而是借机确认:哪些组合本来就不该存在。

二、ArkUI 状态管理解决的是更新机制,不替你定义业务真相

ArkTS 结合 ArkUI 提供声明式 UI、状态管理和渲染控制能力。官方文档对声明式范式的基本解释是:当被框架观察的状态发生变化时,依赖这些状态的 UI 会更新。以状态管理 V1 为例,`@State` 是组件内状态的基础装饰器;它与 `@Prop` 可以建立单向数据同步,与 `@Link`、`@ObjectLink` 可以建立双向同步关系。官方也提供状态管理 V2,用于更明确的输入输出、深度观测和属性级更新等场景。

但“界面会随状态更新”不等于“状态设计就是正确的”。这是迁移时最容易混淆的两层问题。

框架负责观察变化,业务层负责决定什么才是真实状态。比如一个解析结果对象既被结果编辑组件修改,又被确认页修改,还被异步回调直接替换,即使每次修改都能触发 UI 刷新,数据所有权依然是混乱的。用户看到的闪烁和回退,只是所有权冲突的外在表现。

一条业务链路最好有一个明确的状态所有者。页面组件负责把状态呈现出来,把用户意图传回去;网络请求、缓存读取、数据合并和错误归一化放在业务层处理;展示层不要同时维护另一份“看起来差不多”的事实。父子组件之间也应先明确单向输入还是需要回传事件,再选择相应的状态机制,而不是因为某个装饰器写起来方便,就让多个组件都能随意改同一份数据。

这里还有一个迁移陷阱:只验证最终画面,不验证状态恢复。真正的验收用例应该覆盖页面重进、前后台切换、系统回收后的恢复、权限被拒、网络断开、重复操作等节点。不是所有状态都必须持久化,但必须明确哪些只是瞬时 UI 状态,哪些属于用户不能丢失的业务状态。

三、Promise 解决等待,并不自动解决并发竞争

第二类问题来自异步操作。

ArkTS 提供 Promise 等常见异步编程方式,也针对并发场景提供 TaskPool 和 Worker 两种并发编程 API;官方还提出 Sendable 对象模型,用于支持对象在并发实例间的引用传递。这里必须区分三个概念:异步调用让当前流程不必同步等待,并发 API 用于把适合的任务放到并发执行环境,业务时序则决定多个结果到达时谁有资格生效。

仅仅给函数加上 `async`,不会让竞态条件消失。

仍以文件解析为例。用户先选择文件 A,随即切换到文件 B。A 的解析先开始但后完成,B 的解析后开始却先完成。如果两个回调都直接写入页面状态,最终界面可能展示 A 的结果,而当前文件已经是 B。任务没有报错,代码也没有异常,结果却是错的。

这类问题需要业务层规则,而不是再加一个加载动画。常见做法包括:给每次意图生成序号,只接纳当前最新意图的结果;条件变化时取消或废弃旧任务;对提交类动作设置不可重入边界;把成功、失败和取消统一收敛为可判断的结果。选择哪一种取决于业务,不存在适用于所有页面的万能封装。

TaskPool 和 Worker 也不该被当作“性能按钮”。是否使用并发能力,要先看任务是否真的会占用较多计算资源、是否适合离开 UI 线程、数据如何跨并发实例传递、结果怎样回到当前业务状态。一个普通网络请求的等待,与大批量数据解析、图像处理等计算任务不是同一种问题。把所有异步函数都丢进并发 API,只会增加通信、生命周期和错误处理成本。

四、条件编译只能隔离平台差异,不能掩盖业务分叉

跨平台迁移还经常遇到一个诱惑:哪里不一样,就在哪里加一个平台判断。

少量、稳定且不可消除的平台差异,确实需要隔离。例如系统能力是否存在、设备形态是否支持、某个依赖在不同构建目标下是否可用。好的隔离方式,是把差异收束在边界层,对业务层提供一致的能力接口,并为“不支持”定义清楚的降级结果。

坏的条件分支则会一路渗入页面、状态对象和业务流程。到最后,同一个按钮在不同平台走不同校验,同一个字段有两套含义,同一个错误码被解释成两种结果。表面上复用了代码,实际上维护了两条隐藏的业务链。

判断一个分支是否合理,可以问两句话:这是操作系统能力不同,还是我们没有统一业务规则?如果拿掉平台名,这个分支仍然存在吗?如果仍然存在,它大概率不是平台适配问题,而是业务状态本来就没有被定义清楚。

五、迁移完成的标准,是关键行为可以被重复证明

一条链路是否迁移完成,至少应经过三层验证。

第一层是状态覆盖。所有允许状态、转换条件和终态都有对应测试,非法组合不会被 UI 拼出来。

第二层是时序覆盖。人为制造慢请求、乱序返回、重复点击、中断和重试,确认最后生效的结果符合用户最新意图。涉及计算型任务时,再验证 TaskPool 或 Worker 的使用没有破坏数据一致性和生命周期。

第三层是设备与系统场景覆盖。真机验证前后台切换、权限拒绝、弱网、页面重建和不同设备形态。截图对比只能证明外观接近,不能证明链路可靠。

迁移的价值也正在这里。它不是换一种语言把旧端重新写一遍,而是逼团队把长期藏在代码里的规则说清楚:谁拥有状态,谁可以修改,哪个结果有效,失败后如何恢复。

页面复刻是可见的工作,状态与并发治理是决定用户能否信任这条链路的工作。

真正完成的 ArkUI 迁移,应该经得起的不是一次演示,而是用户用各种不按脚本的方式操作之后,系统仍然给出唯一、可解释的结果。

本文由人人都是产品经理作者【小及时】,原创/授权 发布于人人都是产品经理,未经许可,禁止转载。

题图来自Unsplash,基于CC0协议

更多精彩内容,请关注人人都是产品经理微信公众号或下载App
评论
评论请登录
  1. 目前还没评论,等你发挥!