HarmonyOS鸿蒙Next中新请求数据和上次数据相同时,LazyForEach内子组件不更新的问题如何解决

HarmonyOS鸿蒙Next中新请求数据和上次数据相同时,LazyForEach内子组件不更新的问题如何解决

【问题现象】

在实际的业务中,经常遇到两次请求返回数据完成相同的情况,但是要求即使数据相同,也要刷新LazyForEach相关的组件。

因为,尽管第一次和第二次请求返回的数据相同,但是通常会根据第一次返回的数据进行某些处理和相关组件的刷新;第二次请求时,需要重置组件的状态。

如果直接使用返回的相关数据作为LazyForEach的键值,不会触发相关组件的状态刷新。

【背景知识】

LazyForEach键值生成规则:

在LazyForEach循环渲染过程中,系统会为每个item生成一个唯一且持久的键值,用于标识对应的组件。当这个键值变化时,ArkUI框架将视为该数组元素已被替换或修改,并会基于新的键值创建一个新的组件。

LazyForEach提供了一个名为keyGenerator的参数,这是一个函数,开发者可以通过它自定义键值的生成规则。如果开发者没有定义keyGenerator函数,则ArkUI框架会使用默认的键值生成函数,即:

(item: any, index: number) => { return viewId + '-' + index.toString(); }

相关参考文档:

  1. LazyForEach键值生成规则
  2. LazyForEach组件创建规则

【解决方案】

LazyForEach渲染的原理:只有当keyGenerator参数定义的键值发生变化时,ArkUI框架将才会将该数组元素视为已被替换或修改,并会基于新的键值创建一个新的组件。上述问题中,两次请求返回的数据完全相同时,使用返回数据作为键值将不会触发组件刷新。而业务要求只要有新的请求数据,就必须刷新对应的UI组件,即使数据是相同的。这就要求定义一个合适的keyGenerator参数,对相同元素也能产生不同的键值。

下面的解决方案采用在键值生成中加入随机数的方法:

@Entry
@Component
export struct Test11 {
  @State bol:boolean = true
  @State debtData: CommonDataSource<Data> = new CommonDataSource([])
  data1:Data[] = [new Data('分组1 name1','分组1 code1'),new Data('分组1 name2','分组1 code2')]

  aboutToAppear(): void {
    this.debtData.setNewData(this.data1)
  }

  build() {
    Column(){
      Text('重新请求数据').width('100%').height(40).fontColor(Color.Red).onClick(()=>{
        this.getDetData()
      })
      List() {
        LazyForEach(this.debtData, (data: Data, index: number) => {
          ListItem() {
            TestComponent({data:data})
          }.height(80)
          .onClick(()=>{
            data.stockCode = data.stockCode + 'ss'
          })
        }, (value: Data) => JSON.stringify(value) + Math.random())
      }
      .width('100%')
      .layoutWeight(1)
      .listDirection(Axis.Vertical)
      .divider({ strokeWidth: 5, color: 'app.color.dz_root_background' })
    }
  }

  // 获取数据
  getDetData() {
    if(this.bol){
      this.bol = false
      this.debtData.setNewData([new Data('分组1 name1','分组1 code1'),new Data('分组1 name2','分组1 code2')])
    }else {
      this.bol = true
      this.debtData.setNewData([new Data('分组1 name1','分组1 code1'),new Data('分组1 name2','分组1 code2')])
    }
  }
}

@Component
export struct TestComponent {
  @ObjectLink data:Data

  build() {
    Text(this.data.stockCode).fontColor(Color.Black).fontSize(20)
    }
}

@Observed
export class Data {
  stockName: string = ''
  stockCode: string = ''

  constructor(name:string,code:string) {
    this.stockName = name
    this.stockCode = code
  }
}

export class CommonDataSource<T> implements IDataSource {
  private dataArray: T[] = [];
  private listeners: DataChangeListener[] = [];

  constructor(element: T[]) {
    this.dataArray = element;
  }

  public getData(index: number) {
    return this.dataArray[index]
  }

  public getDataList(){
    return this.dataArray
  }

  public totalCount(): number {
    return this.dataArray.length;
  }

  public getIndex(data: T): number {
    return this.dataArray.indexOf(data);
  }

  public addArrayData(data: T[]): void {
    this.dataArray = this.dataArray.concat(data);
    this.notifyDataReload();
  }

  public setNewData(data: T[]): void {
    this.dataArray = [];
    this.addArrayData(data);
  }

  public addData(index: number, data: T[]): void {
    this.dataArray = this.dataArray.concat(data);
    this.notifyDataAdd(index);
  }

  public pushData(data: T): void {
    this.dataArray.push(data);
    this.notifyDataAdd(this.dataArray.length - 1);
  }

  public clearData(): void {
    this.dataArray = [];
  }

  public refresh(): void {
    this.notifyDataReload()
  }

  unregisterDataChangeListener(listener: DataChangeListener): void {
    const pos = this.listeners.indexOf(listener);
    if (pos >= 0) {
      this.listeners.splice(pos, 1);
    }
  }

  registerDataChangeListener(listener: DataChangeListener): void {
    if (this.listeners.indexOf(listener) < 0) {
      this.listeners.push(listener);
    }
  }

  notifyDataReload(): void {
    this.listeners.forEach((listener: DataChangeListener) => {
      listener.onDataReloaded();
    })
  }

  notifyDataAdd(index: number): void {
    this.listeners.forEach((listener: DataChangeListener) => {
      listener.onDataAdd(index);
    })
  }

  notifyDataChange(index: number): void {
    this.listeners.forEach((listener: DataChangeListener) => {
      listener.onDataChange(index);
    })
  }

  notifyDataDelete(index: number): void {
    this.listeners.forEach((listener: DataChangeListener) => {
      listener.onDataDelete(index);
    })
  }

  notifyDataMove(from: number, to: number): void {
    this.listeners.forEach((listener: DataChangeListener) => {
      listener.onDataMove(from, to);
    })
  }
}

解释:

首次运行的代码的结果如下:

点击放大

点击“分组1 code1”和“分子1 code2”,对应属性发生变化。

点击放大

接下来,点击“重新请求数据”,返回的数据和原始数据相同,对应的“分组1 code1ss”和“分子1 code2ss”应该恢复到原始状态,如下:

点击放大


更多关于HarmonyOS鸿蒙Next中新请求数据和上次数据相同时,LazyForEach内子组件不更新的问题如何解决的实战教程也可以访问 https://www.itying.com/category-93-b0.html

1 回复

更多关于HarmonyOS鸿蒙Next中新请求数据和上次数据相同时,LazyForEach内子组件不更新的问题如何解决的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在LazyForEach中,即使两次请求返回的数据相同,也可以通过自定义keyGenerator参数来强制刷新子组件。具体方法是在键值生成中加入随机数,确保每次请求生成的键值不同。例如:

LazyForEach(this.debtData, (data: Data, index: number) => {
  ListItem() {
    TestComponent({data:data})
  }.height(80)
  .onClick(()=>{
    data.stockCode = data.stockCode + 'ss'
  })
}, (value: Data) => JSON.stringify(value) + Math.random())

这样,即使数据相同,生成的键值也会不同,从而触发组件的刷新。

回到顶部