一个双栏工单页里,右侧详情更新为“已复核”,左侧工单列表却还挂着“待复核”的灰色徽标。用户往下滑动再滑回来,徽标又有时变对了。这类现象最容易被当成缓存问题,实际需要先分清:变化有没有进入状态源,列表子节点是否被复用,提取出的 @Builder 究竟拿到了怎样的参数。这篇文章的 BadgeRail Demo 只研究 Repeat + @Builder 的数据联动。示例任务是 BG-041,选中的工单是 RG-108。按下“完成复核”后,业务数据从 PENDING 变成 REVIEWED,右侧详情和左侧徽标都应显示“已复核”。这些都是用来检查代码的预设状态,不是真机测试结果。## 一、同一条数据,出现两张不同的脸大屏主从页面经常给人一种错觉:右侧详情是独立区域,左侧列表也是独立区域,各自更新就好。可在审核业务里,两个区域展示的是同一个对象。右侧修改的是业务事实,左侧徽标只是这个事实的另一种投影。只要状态源发生分叉,页面就会出现前后矛盾。我把问题限定为一个非常具体的变化:RG-108 的状态从 PENDING 改成 REVIEWED。右侧标题、按钮文字、日志和左侧列表行都从同一组 tickets 派生,不能分别维护一份“列表状态”和“详情状态”。这里不涉及服务端同步,也不使用未经验证的系统平行视界接口;主从布局是 ArkUI 的页面内双栏演示。真实应用启用 EasyGo 或其他多设备能力时,需要另行处理路由、窗口生命周期和设备支持范围。还有一个容易漏掉的条件:列表要能够滚动,并启用 Repeat.virtualScroll() 以展示节点复用时的更新约束。官方文档说明,虚拟滚动模式支持 List 等指定滚动容器,需要配合 V2 状态管理,不建议混用 V1 装饰器。这一点比给界面加多少工单更重要,因为我们要定位的不是长列表的吞吐,而是复用后的内容有没有继续跟着变化。## 二、把工单事实收敛到一个状态数组下面先解决数据来源问题。每条工单有固定 id 和 status,页面把数组放在 @Local 中。初始数据保留四张工单,其中 RG-108 尚未复核。为防止把对象属性变化误认为整个数组已经触发了视图更新,业务动作采用替换对象和数组的方式提交新状态。arktsclass TicketBadge { readonly id: string; readonly title: string; readonly status: string; constructor(id: string, title: string, status: string) { this.id = id; this.title = title; this.status = status; }}@Entry@ComponentV2struct BadgePage { @Local selectedId: string = 'RG-108'; @Local tickets: TicketBadge[] = [ new TicketBadge('RG-101', '内容安全审核', 'REVIEWED'), new TicketBadge('RG-104', '隐私合规检查', 'REVIEWED'), new TicketBadge('RG-108', '三方 SDK 评估', 'PENDING'), new TicketBadge('RG-112', '上架资料核对', 'PENDING') ];}这段为结构摘录,后面的 build、@Builder 和事件方法按章节放回同一个 BadgePage 组件即可。readonly 保护的是 id 等字段不被随意改写,不能拿它替代响应式追踪。对于本示例,把一个列表项的状态更新落实为一次明确的数组引用变化,可以让“业务数据更新了没有”变得容易判断。点击“完成复核”只应该改变 RG-108 的 status,不能顺手移动它的位置,更不能把 selectedId 改成下一张工单。审核状态和当前查看的对象是两个独立维度。即使将来详情页作为真正的系统平行视界页面存在,这个业务事实也不该因窗口拆分而分叉。## 三、最值得检查的是 Builder 参数,而不是颜色Repeat 的一项容易忽略的约束来自官方指南:它与 @Builder 混用时,应该将完整的 RepeatItem 传给 Builder。若仅传 ri.item 或 ri.index,在节点复用、数据更新的场景下就可能产生视图更新异常。下面以完整项对象作为参数,避免把响应关系在组件封装处截断。arkts// BadgePage 内部:完整传入 RepeatItem,而非 ri.item@BuilderbadgeRow(ri: RepeatItem<TicketBadge>) { Row({ space: 8 }) { Text(ri.item.id) Text(ri.item.title) Text(ri.item.status === 'REVIEWED' ? '已复核' : '待复核') .fontColor(ri.item.status === 'REVIEWED' ? Color.Green : Color.Gray) }}// BadgePage.build() 的列表局部摘录List() { Repeat<TicketBadge>(this.tickets) .each((ri: RepeatItem<TicketBadge>) => { ListItem() { this.badgeRow(ri) } }) .key((item: TicketBadge) => item.id) .virtualScroll()}````.key()` 也要保持稳定,但它在本篇只是渲染身份约束,不是主要难点。左侧排序没有变化,业务更新的是同一 RG-108 的状态,key 因此仍是原来的 RG-108。如果错误地把 `status` 拼进 key,PENDING 变为 REVIEWED 时框架会把它视为另一个身份,从而触发不必要的重建,也更难看清复用导致的更新问题。更关键的是 `badgeRow(ri)`,而不是 `badgeRow(ri.item)`。前者保留了 `RepeatItem` 关联;后者看着少传一层参数,却可能把框架用于更新子项的数据关系截断。开发者可以把这两种写法放进对照分支观察,但不能因为一种写法“某次看起来也更新了”就断言在所有滚动和复用条件下可靠。![](https://i-blog.csdnimg.cn/direct/7ee10eba0f7f42fb92131fdb2ec54c78.png)## 四、用一次状态提交串起左右两栏既然问题是徽标内容没及时更新,排查就不需要先上复杂的性能工具。我们先让一次“完成复核”操作产生可追踪的业务事件,再由新的数组状态驱动左右区域。下面这段业务方法用不可变更新改写 RG-108;真实系统里的提交成功与否应由 Repository 或服务端返回,示例只模拟本地成功路径。arkts// BadgePage 内部;“完成复核”按钮触发private markReviewed(): void { this.tickets = this.tickets.map((item: TicketBadge) => { if (item.id !== this.selectedId) { return item; } return new TicketBadge(item.id, item.title, ‘REVIEWED’); }); const current = this.tickets.find((t: TicketBadge) => t.id === this.selectedId); console.info([BadgeRail] task=BG-041 selected=${this.selectedId}); console.info([BadgeRail] status=${current?.status ?? 'MISSING'});}```这段代码不保留旧的可写引用,因此检查更直接:如果 status=REVIEWED 已经出现在日志里,而左侧仍显示待复核,就要集中查看渲染输入、@Builder 的入参、节点复用与页面状态装饰器的组合。如果日志还停在 PENDING,则应先检查按钮调用路径或数据更新条件;此时盯着 Builder 改样式只能掩盖真正的原因。业务方法没有抛出网络异常,因为这里没有网络。后续接入真实接口后,应该明确 SUBMITTING / REVIEWED / FAILED,在失败时保留旧状态或使用可回滚的乐观更新,同时对重复点击做门禁。不要用一个 try/catch 把所有服务端错误消化成“更新完成”。## 五、演示结果与可执行的检查顺序本轮统一的预期演示数据是:项目 BadgeRail,页面 BadgePage.ets,任务 BG-041,选中 RG-108,原状态 PENDING,按钮“完成复核”,目标状态 REVIEWED,系统界面时间 10:41。示例日志是 [BadgeRail] task=BG-041 selected=RG-108 和 [BadgeRail] status=REVIEWED。它们应当在文章、IDE 演示图和手机界面里一致,不作为编译或真机验证证据。如果安排一轮真正测试,我会先比较“未滚动直接更新”和“滑动列表使节点复用后再更新”。前者可以暴露数据源更新问题,后者更容易暴露只传值给 Builder 的边界。随后再把 RG-108 从可见区域移出,更新状态,再滚回原处确认徽标。整个过程应记录两条事实:数据项 status 已变成什么,最终渲染呈现了什么。不能拿一张静态截图代替这两条证据。对长列表来说,还需要注意缓存大小、滑动位置、数据增删以及页面重新出现的情况。官方指南提到 virtualScroll 模式的可用容器及渲染限制;自定义 Builder 如果再包裹复杂子组件,更应核实数据绑定有没有保留完整项对象。这里没有提交性能数字,也没有声称某种写法提速百分之多少,因为没有在目标设备上做采样。## 六、真正的问题不是徽标,而是数据关系被切断本文与“列表排序后选择项错位”是两件不同的事。前者研究稳定 ID 与索引,本文研究的是 相同 ID 的状态发生变化,Repeat 复用的子项能否及时渲染新值。两者会在同一个复杂工作台同时出现,但根因、检查步骤和修复位置不能混为一谈。当前示例不持有长期监听、文件句柄或异步截图对象,页面退出时没有额外资源需要手动释放。若业务实际接入后台状态订阅,记得在页面生命周期结束时退订,并通过请求 ID 或版本号拒绝迟到回调。若一条工单已经无权限处理,即使 UI 显示了 REVIEWED,也必须由后端继续拦截非法提交;视觉徽标不是权限凭证。最值得留下的一条检查原则是:先看数据源,再看 RepeatItem,最后才看颜色和布局。 当 Builder 封装没有丢掉框架需要追踪的数据关系,双栏主从区域才能持续展示同一条业务事实。参考资料: 华为 Repeat 官方指南、华为 List 官方指南。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐