HarmonyOS鸿蒙Next中数据源变化后,List组件内容未同步变化该如何解决
HarmonyOS鸿蒙Next中数据源变化后,List组件内容未同步变化该如何解决
【问题现象】
使用LazyForEach和@Observed修饰对象时,当列表对象内容修改后,UI也会随之变化。但刷新列表时,上一次的内容修改仍然存在。
【背景知识】
LazyForEach从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。当在滚动容器中使用了LazyForEach,框架会根据滚动容器可视区域按需创建组件,当组件滑出可视区域外时,框架会进行组件销毁回收以降低内存占用。
【定位思路】
根据代码:
OrderModel.ets
export class OrderListParams {
communityId: string = ''
matterName: string = ''
current: number = 0
size: number = 15
}
[@Observed](/user/Observed)
class OrderResult {
records: OrderInfo[] = []
total: number = -1
current: number = 0
pages: number = -1
}
[@Observed](/user/Observed)
export class OrderInfo {
id: string
matterFlag: number
matterFlagName: string
matterName: string
matterRemark: string
constructor() {
this.id = ''
this.matterFlag = -1
this.matterFlagName = ''
this.matterName = ''
this.matterRemark = ''
}
}
LazyDataSource.ets
class BasicDataSource <T> implements IDataSource {
private listeners: DataChangeListener[] = [];
public totalCount(): number {
return 0;
}
public getData(index: number): T | undefined {
return undefined;
}
register...
unregister...
notify...
}
[@Observed](/user/Observed)
export default class LazyDataSource<T> extends BasicDataSource<T> {
dataArray: T[] = [];
...
public pushData(data: T): void {
this.dataArray.push(data);
this.notifyDataAdd(this.dataArray.length - 1);
}
public pushArrayData(newData: Array<T>): void {
this.clear();
this.dataArray.push(...newData);
this.notifyDataReload();
}
public clear(): void {
this.dataArray.splice(0, this.dataArray?.length)
this.notifyDataReload()
}
...
}
[@Component](/user/Component)
struct OrderListItem {
@ObjectLink data: OrderInfo
build() {
Column(){
Text(this.data.id)
Text(this.data.matterName)
Text(this.data.matterFlagName)
Text(this.data.matterRemark)
}.onClick(()=>{
this.data.matterFlagName = this.data.matterFlagName + 'z';
})
}
}
OrderListItem.ets
[@Component](/user/Component)
struct OrderListItem {
@ObjectLink data: OrderInfo
build() {
Column(){
Text(this.data.id)
Text(this.data.matterName)
Text(this.data.matterFlagName)
Text(this.data.matterRemark)
}.onClick(()=>{
this.data.matterFlagName = this.data.matterFlagName + 'z';
})
}
}
Index.ets
import OrderModel, { OrderInfo, OrderListParams } from './OrderModel';
import { promptAction } from '@kit.ArkUI';
import LazyDataSource from './LazyDataSource';
[@Entry](/user/Entry)
[@Component](/user/Component)
struct Index {
@State myDataSource: LazyDataSource<OrderInfo> = new LazyDataSource();
private getListData(p: OrderListParams) {
return new Promise<string>((resolve, reject) => {
setTimeout(() => {
const gap = ((p.current - 1) * p.size)
const llt: OrderInfo[] = []
for (let index = 0 + gap; index < 15 + gap; index++) {
const tmp = new OrderInfo()
tmp.id = 'id' + index
tmp.matterFlag = 1024 + index
tmp.matterFlagName = 'matterFlagName' + index
tmp.matterName = 'matterName' + index
tmp.matterRemark = 'matterRemarkmatterR------rRemark' + index
llt.push(tmp)
}
this.myDataSource.pushArrayData(llt)
resolve('');
}, 1 * 1000)
})
}
build() {
Column() {
Button('刷新').onClick(()=>{
const p = new OrderListParams()
p.current = 1
p.size = 15
this.getListData(p)
})
List({ space: 12 }) {
LazyForEach(this.myDataSource, (item: OrderInfo) => {
ListItem() {
OrderListItem({ data: item })
}
.width('100%')
.onClick(()=>{
item.matterRemark = item.matterRemark + 'w'
})
}, (item: OrderInfo)=>{return item.id + item.matterRemark + item.matterFlagName})
}
.width('100%')
.layoutWeight(1)
}
.width('100%')
.height('100%')
}
}
这段代码中使用了LazyForEach、@Observed以及@ObjectLink方式构建列表内容,并在Index.ets文件中实现了“刷新按钮”的功能。可以看到,该方法调用了LazyDataSource的pushArrayData方法。
在LazyDataSource.ets文件中,pushArrayData方法首先调用clear方法清空dataArray数组,并调用notifyDataReload方法。然后将新数组的数据push到dataArray中,并再次调用notifyDataReload方法。
从背景知识中的渲染控制可以知道,当LazyForEach的数据源发生变化时,LazyForEach会根据每个组件绑定的key进行重建(即Index.ets文件中的第47行)。
根因分析:分析Index.ets文件中的getListData方法传入的新数据可知,第47行的key值在调用getListData方法后不会改变,因此调用notifyDataReload方法后,对应的组件只会从缓存中获取,而不是重建,即表现为UI不刷新。
【解决方案】
解题方向有两种:
-
给数据类型添加一个时间戳或类似的其他变量,并将该变量应用到key值中:
在OrderModel.ets文件中,给OrderInfo对象添加timestamp属性。然后在Index.ets文件的getListData方法中,构造数据时为timestamp赋值。在LazyForEach的keyGenerator中将timestamp关联到组件key值中。这样每次触发“刷新按钮”时,item的key值会更新,从而调用notifyDataReload方法后触发组件重建,使UI刷新。修改后的代码如下:
OrderModel.ets
... [@Observed](/user/Observed) export class OrderInfo { id: string ... + timestamp: number constructor() { this.id = '' ... + this.timestamp = 0 } } ...
Index.ets
[@Entry](/user/Entry) [@Component](/user/Component) struct Index { @State myDataSource: LazyDataSource<OrderInfo> = new LazyDataSource(); private getListData(p: OrderListParams) { setTimeout(() => { const gap = ((p.current - 1) * p.size) const llt: OrderInfo[] = [] for (let index = 0 + gap; index < 15 + gap; index++) { const tmp = new OrderInfo() tmp.id = 'id' + index ... + tmp.timestamp = new Date().getTime() llt.push(tmp) } this.myDataSource.pushArrayData(llt) resolve('') }, 1 * 1000) }) } build() { Column() { Button('刷新').onClick(()=>{ ... }) List({ space: 12 }) { LazyForEach(this.myDataSource, (item: OrderInfo) => { ListItem() { OrderListItem({ data: item }) } .width('100%') .onClick(()=>{ item.matterRemark = item.matterRemark + 'w' }) + }, (item: OrderInfo)=>{return item.id + item.matterRemark + item.matterFlagName + item.timestamp.toString()}) } .width('100%') .layoutWeight(1) } .width('100%') .height('100%') } }
-
对LazyDataSource.ets文件中的pushArrayData方法进行修改,不使用notifyDataReload方法进行重载,改用pushData方法触发notifyDataAdd通知列表进行刷新,此方法改动较小。修改后代码如下:
LazyDataSource.ets
class BasicDataSource <T> implements IDataSource { ... } [@Observed](/user/Observed) export default class LazyDataSource<T> extends BasicDataSource<T> { ... public pushData(data: T): void { this.dataArray.push(data); this.notifyDataAdd(this.dataArray.length - 1); } public pushArrayData(newData: Array<T>): void { + this.dataArray.splice(0, this.dataArray?.length); + let count = newData.length; + for (let i = 0; i < count; i++) { + this.pushData(newData[i]); + } } public deleteData(index: number): void { this.dataArray.splice(index, 1); this.notifyDataDelete(index); } public clear(): void { this.dataArray.splice(0, this.dataArray?.length) this.notifyDataReload() } ... }
【总结】
- notifyDataReload方法会通知LazyForEach需要重建所有子节点,而LazyForEach会将原所有数据项和新所有数据项一一做键值比对,若有相同key值则使用缓存,若key值不同则重新构建,即当key值不变时UI不会刷新。由此可见,notifyDataReload属于高消耗操作,非必要场景应避免使用该操作。
- 当使用LazyForEach渲染列表时,应结合组件复用,并优先使用@Track装饰器精准控制刷新范围,避免非必要的组件重建。
更多关于HarmonyOS鸿蒙Next中数据源变化后,List组件内容未同步变化该如何解决的实战教程也可以访问 https://www.itying.com/category-93-b0.html
更多关于HarmonyOS鸿蒙Next中数据源变化后,List组件内容未同步变化该如何解决的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
在HarmonyOS鸿蒙Next中,当数据源变化后,List组件内容未同步变化的问题可以通过以下两种方式解决:
1. 添加时间戳到key值
- 在
OrderInfo
对象中添加timestamp
属性。 - 在
getListData
方法中为timestamp
赋值。 - 在
LazyForEach
的keyGenerator
中将timestamp
关联到组件key值中。 - 这样每次刷新时,
item
的key值会更新,触发组件重建,使UI刷新。
2. 修改pushArrayData
方法
- 不使用
notifyDataReload
方法进行重载,改用pushData
方法触发notifyDataAdd
通知列表进行刷新。 - 修改后的
pushArrayData
方法会逐个添加新数据,确保UI同步更新。
这两种方法都能有效解决数据源变化后List组件内容未同步变化的问题。