Bug排查在軟件開發過程中扮演著至關重要的角色,本文采用日記形式記錄了Bug排查的全過程,通過這種方式可以更加真實、詳細地記錄問題,便于后續追溯和經驗沉淀。
Bug背景
在使用HarmonyOS的ArkUI框架開發一個卡片管理應用時,遇到了List列表界面不刷新的問題。具體表現為,雖然數據已經更新,但界面并未同步刷新顯示。比如在子頁面中明明添加了卡片并更新了數據,但是頁面返回后,看到的卡片數量并未發生變化。
環境
- 設備:HarmonyOS模擬器
- 系統版本:HarmonyOS 5.0
- 應用框架:ArkUI
復現條件
在多個界面的狀態共享時,使用了@Provider
和@Consumer
裝飾器,同時對類和成員變量使用了@ObservedV2
和@Trace
裝飾,從而實現了數據的共享和追蹤。在添加、刪除或更新卡片數據時,數據操作成功,但List列表并未刷新顯示最新的數據。
這一行就是問題所在。之前的 uid 一直是同一個。。。,結果導致數據變化,UI不會觸發刷新。
之前這里的寫法如下:
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也會隨之變化,從而實現界面的正確刷新。
修復代碼具體改動
ForEach(this.cardViewModel?.cardDeckList ?? [], (item: CardDeckData,index) => {}, (item: CardDeckData) => item.uid + '_' + JSON.stringify(item))
要保證數據更新后,不但這個key是唯一的,且是變化了的。
測試驗證
修改代碼后,進行了單元測試和集成測試,確認問題已解決。
坑二:列表數組中元素值改變界面不刷新
再次遇到數據不刷新的問題,通過調試發現需要對cardDeckList
進行重新賦值才能刷新界面。
// 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 = namerec.desc = descrec.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
,這樣可以確保對象引用發生變化,從而實現界面的正確刷新。
修復代碼具體改動
DBDeckApi.queryAllCardDeck().then((result) => {Log.debug(result?.length)this.cardDeckList = result ?? [];const newList = [...this.cardDeckList];this.cardDeckList = newList;
})
測試驗證
修改代碼后,進行了單元測試和集成測試,確認問題已解決。
影響范圍
- 功能模塊:卡片管理模塊
- 用戶體驗:用戶無法看到最新的卡片數量,影響操作感受
- 業務邏輯:無法準確顯示業務數據,影響應用功能
經驗總結
技術收獲
- 使用
@ObservedV2
和@Trace
裝飾類和成員變量,可以方便地追蹤和處理數據變化。 ForEach
循環的key生成器必須能夠正確地反映數據變化,從而保證界面的正確刷新。- 重新賦值對象以改變其引用,可以確保界面能夠正確檢測到數據變化并進行刷新。
流程優化
- 使用代碼審查(Code Review)和測試策略可以避免類似的問題。
- 在開發過程中,應充分考慮框架特性和代碼邏輯,避免不必要的“坑”。
- 通過調試器和日志分析工具等工具,可以快速定位問題并進行修復。
附錄
相關技術文檔鏈接
- ArkUI ForEach文檔
- HarmonyOS數據共享文檔
參考工具清單
- HarmonyOS調試器
- Log分析工具(如Logcat)
- Sentry(錯誤監控系統)
通過以上記錄,希望能夠幫助其他開發者避免在使用ArkUI時遇到類似的問題,同時也為提升自身的代碼質量和開發效率提供了一定的參考。