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不刷新。

【解决方案】

解题方向有两种:

  1. 给数据类型添加一个时间戳或类似的其他变量,并将该变量应用到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%')
      }
    }
    
  2. 对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()
      }
      ...
    }
    

【总结】

  1. notifyDataReload方法会通知LazyForEach需要重建所有子节点,而LazyForEach会将原所有数据项和新所有数据项一一做键值比对,若有相同key值则使用缓存,若key值不同则重新构建,即当key值不变时UI不会刷新。由此可见,notifyDataReload属于高消耗操作,非必要场景应避免使用该操作。
  2. 当使用LazyForEach渲染列表时,应结合组件复用,并优先使用@Track装饰器精准控制刷新范围,避免非必要的组件重建。

更多关于HarmonyOS鸿蒙Next中数据源变化后,List组件内容未同步变化该如何解决的实战教程也可以访问 https://www.itying.com/category-93-b0.html

1 回复

更多关于HarmonyOS鸿蒙Next中数据源变化后,List组件内容未同步变化该如何解决的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在HarmonyOS鸿蒙Next中,当数据源变化后,List组件内容未同步变化的问题可以通过以下两种方式解决:

1. 添加时间戳到key值

  • OrderInfo对象中添加timestamp属性。
  • getListData方法中为timestamp赋值。
  • LazyForEachkeyGenerator中将timestamp关联到组件key值中。
  • 这样每次刷新时,item的key值会更新,触发组件重建,使UI刷新。

2. 修改pushArrayData方法

  • 不使用notifyDataReload方法进行重载,改用pushData方法触发notifyDataAdd通知列表进行刷新。
  • 修改后的pushArrayData方法会逐个添加新数据,确保UI同步更新。

这两种方法都能有效解决数据源变化后List组件内容未同步变化的问题。

回到顶部