Bug排查在软件开发过程中扮演着至关重要的角色,本文采用日记形式记录了Bug排查的全过程,通过这种方式可以更加真实、详细地记录问题,便于后续追溯和经验沉淀。
Bug背景
在使用HarmonyOS的ArkUI框架开发一个卡片管理应用时,遇到了List列表界面不刷新的问题。具体表现为,虽然数据已经更新,但界面并未同步刷新显示。比如在子页面中明明添加了卡片并更新了数据,但是页面返回后,看到的卡片数量并未发生变化。

环境
- 设备:HarmonyOS模拟器
- 系统版本:HarmonyOS 5.0
- 应用框架:ArkUI
复现条件
在多个界面的状态共享时,使用了@Provider
和@Consumer
装饰器,同时对类和成员变量使用了@ObservedV2
和@Trace
装饰,从而实现了数据的共享和追踪。在添加、删除或更新卡片数据时,数据操作成功,但List列表并未刷新显示最新的数据。

这一行就是问题所在。之前的 uid 一直是同一个。。。,结果导致数据变化,UI不会触发刷新。
之前这里的写法如下:
typescript
ForEach(this.cardViewModel?.cardDeckList ?? [], (item: CardDeckData,index) => {
Row() {
CardDeck({})
}
}, (item: CardDeckData) => item.uid)
问题出在这个ForEach循环的最后一个参数item.uid上。
坑一:ForEach循环的key生成器
在使用ForEach
循环渲染列表时,注意到最后一个参数------key生成器。这个参数用于生成列表项的唯一标识,如果设置不当,就可能导致列表项无法正确更新。
问题
数据已经更新,但通过ForEach
循环渲染的列表界面并未刷新显示最新的数据。
分析与假设
分析关键代码片段,发现ForEach
循环的key生成器仅使用了item.id
,这样虽然可以保证唯一性,但当数据发生变化时,key并未随之更新,导致界面无法刷新。
解决方案
通过修改key生成器的逻辑,将item.id
与更新的成员变量的值进行组合,确保当数据变化时,key也会随之变化,从而实现界面的正确刷新。

修复代码具体改动
javascript
ForEach(this.cardViewModel?.cardDeckList ?? [], (item: CardDeckData,index) => {}, (item: CardDeckData) => item.uid + '_' + JSON.stringify(item))
要保证数据更新后,不但这个key是唯一的,且是变化了的。
测试验证
修改代码后,进行了单元测试和集成测试,确认问题已解决。
坑二:列表数组中元素值改变界面不刷新
再次遇到数据不刷新的问题,通过调试发现需要对cardDeckList
进行重新赋值才能刷新界面。
javascript
// viewmodol/cardViewModol.ets
import DBDeckApi from "../common/api/deckApi";
import { TBCardDeckEntity } from "../model/TBCardDeck";
import { getCurrentDateTime } from "../utils/dateUtil";
import { Log } from "../utils/logutil";
import { ToastUtil } from "../utils/ToastUtil";
@ObservedV2
export class CardDeckData{
@Trace uid: number | undefined = 0
@Trace name: string| undefined='';
@Trace desc: string| undefined = ''
// 卡片张数
@Trace cardNum: number | undefined = 0
// 已学习张数
@Trace readNum: number | undefined = 0
// 学习时长
@Trace timeLen: number | undefined = 0
// 创建时间
@Trace createTime: string | undefined = ''
// 更新时间
@Trace updateTime: string | undefined = ''
// 开始学习时间
@Trace startTime: string | undefined = ''
// 结束学习时间
@Trace endTime: string | undefined = ''
}
@ObservedV2
export class CardViewModel {
private static instance: CardViewModel | null = null;
@Trace cardDeckList: CardDeckData[] = []
static getInstance(): CardViewModel {
if (!CardViewModel.instance) {
CardViewModel.instance = new CardViewModel();
}
return CardViewModel.instance;
}
/**
* 添加卡组
* @param timelineId 时光轴ID
*/
addCardDeck(name:string, desc:string) {
const rec = new TBCardDeckEntity()
rec.name = name
rec.desc = desc
rec.createTime = getCurrentDateTime()
DBDeckApi.insertCardDeck(rec).then((number) => {
if (number < 0) {
Log.error("insertCardDeck error,code=%{public}d", number)
ToastUtil.showError("添加失败")
} else {
Log.debug("insertCardDeck ok,code=%{public}d", number)
this.reloadDeckDbData()
ToastUtil.showSuccess("添加成功")
}
})
}
/**
* 从数据库从新加载卡组数据列表
*/
reloadDeckDbData(){
//从数据库获取数据
DBDeckApi.queryAllCardDeck().then((result) => {
Log.debug(result?.length)
this.cardDeckList = result ?? [];
const newList = [...this.cardDeckList];
this.cardDeckList = newList;
// 生成新数组,确保元素为全新实例
//const newList = result?.map(item => ({ ...item })) ?? [];
/*
this.cardDeckList = result?.map(item => ({
uid: item.uid ?? 0,
name: item.name ?? '',
desc: item.desc ?? '',
cardNum: item.cardNum,
readNum: item.readNum,
createTime: item.createTime,
updateTime: item.updateTime,
startTime: item.startTime,
endTime: item.endTime
} as CardDeckData)) ?? [];
* */
/*
this.cardDeckList = result?.map(entity => {
const data = new CardDeckData();
// 显式属性映射(示例)
data.uid = entity.uid ?? undefined;
data.name = entity.name ?? ''; // 处理 undefined 转空字符串
data.desc = entity.desc ?? '';
data.cardNum = entity.cardNum ?? 0;
data.readNum = entity.readNum ?? 0;
data.createTime = entity.createTime!;
data.updateTime = entity.updateTime!;
return data;
})?? [];
* */
})
}
问题
在添加、删除或更新卡片数据后,虽然数据已经变化,但List列表并未刷新显示最新的数据。
必须:非得这么红框里再次赋值才可以刷新啊。显得很低效,但也很无奈。说好的@ObservedV2和@Trace装饰类和成员变量,怎么没观察到成员变量的变化呢?
分析与假设
在reloadDeckDbData
方法中,直接将数据库查询到的数据赋值给cardDeckList
,但由于赋值的对象引用没有变化,导致界面未能检测到数据变化。
解决方案
通过重新生成一个新的数组,并将查询到的数据赋值给新的数组,然后再将新的数组赋值给cardDeckList
,这样可以确保对象引用发生变化,从而实现界面的正确刷新。
修复代码具体改动
javascript
DBDeckApi.queryAllCardDeck().then((result) => {
Log.debug(result?.length)
this.cardDeckList = result ?? [];
const newList = [...this.cardDeckList];
this.cardDeckList = newList;
})
测试验证
修改代码后,进行了单元测试和集成测试,确认问题已解决。
影响范围
- 功能模块:卡片管理模块
- 用户体验:用户无法看到最新的卡片数量,影响操作感受
- 业务逻辑:无法准确显示业务数据,影响应用功能
经验总结
技术收获
- 使用
@ObservedV2
和@Trace
装饰类和成员变量,可以方便地追踪和处理数据变化。 ForEach
循环的key生成器必须能够正确地反映数据变化,从而保证界面的正确刷新。- 重新赋值对象以改变其引用,可以确保界面能够正确检测到数据变化并进行刷新。
流程优化
- 使用代码审查(Code Review)和测试策略可以避免类似的问题。
- 在开发过程中,应充分考虑框架特性和代码逻辑,避免不必要的"坑"。
- 通过调试器和日志分析工具等工具,可以快速定位问题并进行修复。
附录
相关技术文档链接
参考工具清单
- HarmonyOS调试器
- Log分析工具(如Logcat)
- Sentry(错误监控系统)
通过以上记录,希望能够帮助其他开发者避免在使用ArkUI时遇到类似的问题,同时也为提升自身的代码质量和开发效率提供了一定的参考。