针对列表卡顿优化方案

5 阅读2分钟

针对列表的优化常见方案###

    在使用列表的时候,有时在列表比较复杂时,会出现滑动卡顿情况。那么针对列表优化整理了一下几个简单方案,分为复用机制,懒加载机制,冻结功能配置,设置指定的缓存区大小,减少条目的布局节点数;

    针对优化的方案版本代码是基于 api18+,同时使用的是 v2版本。

首先需要了解到的功能点如下:

Repeat:文档中心

冻结功能:文档中心

@Reusablev2:文档中心

优化功能整理如下:

1.懒加载机制

在列表中使用 Repeat 时,针对简短的列表想要直接将全部的item条目都创建的话(类似于ForEach),就无需配置 .virtualScroll({ totalCount: this.lists.length, });,如果列表数据较多,不想一次加载完成,也就需要配置,并设置对应的totalCount长度;实例代码如下:

    List({ space: 10 }) {
      Repeat<ListModule>(this.lists)
        .each((ri) => {
             //这个地方设置页面的 item 布局
        })
        .key((item: ListModule, index: number) => {
          return item.id;//这个地方是 key,需要保持唯一性
        })
        //这个地方设置了懒加载,
        .virtualScroll({ totalCount: this.lists.length, });
    }
    .listDirection(Axis.Vertical)
    .scrollBar(BarState.Off)
    .width('100%')
    .height('100%')
  @Builder
  verifySubmitParams2(ri: RepeatItem<ListModule>) {
    Column() {
      Text(` id ${ri.item.id} ... ${ri.item.info}.. ${ri.index}`)
        .fontSize(20)
        .fontColor('#333').onClick(() => {
        console.log('当前的 id ${module.id} ... 当前的 值 ${module.info}')
      })
    }
    .width('100%')
    .padding(10)
    .border({ radius: 10, color: '#f7f7f7' })
    .onAppear(() => {
      console.log('--------- > list onAppear ' + ri.index)
    })
  }

设置懒加载时,加载触发的item条目创建日志:

10087-10087   I     --------- > list start 50
10087-10087   I     --------- > list onAppear 0
....
10087-10087   I     --------- > list onAppear 27

不设置懒加载时,触发item条目的创建日志:

23-12423   I     --------- > list start 50
12423-12423   I     --------- > list onAppear 0
....
12423-12423   I     --------- > list onAppear 49

    因此在平时列表数据较多的情况下还是建议使用懒加载模式,避免一下子将页面数据,全部创建渲染出来,造成不必要的资源浪费。

    但是针对列表数据很少的,就不用设置懒加载模式了;

特别注意:在 Repeat+@Builder 结合时,传递的参数引用一定要是RepeatItem,如果传递RepeatItem.item 或者其他值;将会出现一下问题:

1.传递的如果是RepeatItem.item 会因为复用引发数据错乱问题;
2.在外界更改数据后,由于复用的位置错乱问题,将无法准确的同步到 builder 中;

3.获取到的下标也是错误的,由于复用错乱问题;

解决方案:

1.直接传递RepeatItem引用;

2.如果非要传递RepeatItem.item,可以在@Builder 方法的参数用 Binding包裹;代码如下:

  @Builder
  verifySubmitParams3(ri: Binding<ListModule>) {
    Column() {
      Text(` id ${ri.value.id} ... ${ri.value.info}`)
        .fontSize(20)
        .fontColor('#333').onClick(() => {
        console.log(`当前的 id ${ri.value.id} ... 当前的 值 ${ri.value.info}`)
      })
    }
    .width('100%')
    .padding(10)
    .border({ radius: 10, color: '#f7f7f7' })
  }

  //调用点代码
  this.verifySubmitParams3(UIUtils.makeBinding<ListModule>(() => ri.item))

2.复用机制、@Reusablev2配置

    针对 Repeat 是默认自带容器复用逻辑,如果想要去掉容器复用,可以设置如下配置;

.virtualScroll({ totalCount: this.lists.length,reusable:false });
//reusable:false 默认情况下是 true ,改成 false 后,就是关闭复用;

Repeat+@Builder 的组合属于容器的复用;如果在item 的条目使用的是@ComponentV2,为了避免组件频繁创建销毁可以设置其复用能力;如下:

@ComponentV2
@ReusableV2
export struct CommonListItem {
  aboutToReuse(): void {
    //被服用
    console.log('------- > common item aboutToReuse ' + this.item)
  }

  aboutToRecycle(): void {
    //被回收
    console.log('------- > common item aboutToRecycle ' + this.item)
  }

 build() {
    }
}

    如果组件已经创建,再次复用的时候,就不会再次创建;同样生命周期,也只会走aboutToReuse和aboutToRecycle 方法;(前提在reusable:false的情况下)

如何选择 使用Repeat 默认的复用机制,还是使用@ReusableV2呢?个人理解

1.需求是否需要监听生命周期;如果有就可以使用@ComponentV2+@ReusableV2的复用形式,同时设置 reusable:false;

2.如果不关注生命周期变化;一般直接使用@Builder +reusable:true(默认 true)即可;

如果 item条目是@ComponentV2+reusable:true 也同样支持;

一句话:是否关心生命周期的变化,需要根据生命周期做相应需求处理,就使用@ComponentV2+@Reusable;否则使用@Builder+reusable:true 即可;

3.冻结功能(freezeWhenInactive)

    冻结功能针对列表多种状态,一般是使用了 Repeat 的template 模板,并设置了缓存个数的前提下,才会设置冻结功能来优化列表;

    冻结功能简单理解就是在组件非激活状态下;如果组件内有状态发生改变,将无法生效(或者监听到);直到组件进入激活状态,才可以接受到状态改变;

**注意:**如果非激活状态下,多次触发状态改变。在进入激活状态后会相应最后一次改变的值;

该功能的优点:在 Item 条目处于缓存区后,如果状态改变,该条目并不会刷新布局;或者说 A页面跳转到 B 页面后,在 B页面更改状态,A页面响应状态改变刷新布局,只有再次可见后才刷新布局;

列表使用场景:列表存在多个不同的类型下;列表存在移除条目的情况下;(都要在存在模板和缓存的前提下)代码如下:

设置冻结功能代码:

@ComponentV2({ freezeWhenInactive: true })
export struct FreezeWhenInactive {
  @Param @Require param: ListModule
  @Param @Require value: number
  @Monitor('value')
  onValueChange(monitor: IMonitor) {
    console.log('--------- > list FreezeWhenInactive ' + this.param.info)
  }
  build() {
    Column() {
      Text(`当前的 id ${this.param.id}`)
        .fontSize(20)
        .fontColor('#333')

      Text(`当前的 值 ${this.param.info}`)
        .fontSize(20)
        .fontColor('#333')
    }
    .width('100%')
    .border({ radius: 10, color: '#fff' })
    .padding({
      left: 10,
      right: 10,
      top: 20,
      bottom: 20
    })
  }
}

列表代码:

 @Builder
  verifyFrez() {
    Column() {
      Text("点击更改可见的值")
        .fontSize(20)
        .fontColor('#333')
        .onClick(() => {
          //验证在冻结类型缓存后,更改值是否触发更新
          this.screenWidth++;
          this.lists[0].info = '更新冻结布局的值 ' + this.screenWidth;
        });

      List({ space: 10 }) {
        Repeat<ListModule>(this.lists)
          .each((ri) => {
            // FreezeWhenInactive({ param: ri.item, value: this.screenWidth })
            this.verifySubmitParams2(ri);
            // this.verifySubmitParams3(UIUtils.makeBinding<ListModule>(() => ri.item))
          })
          .key((item: ListModule, index: number) => {
          return item.id;
        })
          .template('1', (ri) => {
            FreezeWhenInactive({ param: ri.item, value: this.screenWidth });
            // this.verifySubmitParams2(ri)
          }, { cachedCount: 2 })
          .templateId((item: ListModule, index: number) => {
            if (index == 0) {
              return '1';//使用模板template id =='1'的布局
            }
            return '';//使用默认的列表也就是 each 下的布局
          })
          .virtualScroll({ totalCount: this.lists.length }); //当添加懒加载后,不会全部的数据整体加载
      }
      .width('100%')
      .height('100%');
    }
    .width('100%')
    .height('100%')
  }

注意:一定是在设置template 模板布局的情况下;

4.手动设置缓存大小

    在Repeat 中使用模板,并指定缓存的大小代码如下:

.template('1', (ri) => {
            FreezeWhenInactive({ param: ri.item, value: this.screenWidth });
          }, { cachedCount: 2 })

设置cachedCount即可设置模板缓存的个数;

5.减少item 的布局节点数

在列表中的 item 条目,尽可能的减少 view 树的节点个数,如果节点个数太多,绘制时间 就会变长,最终导致丢帧 引发卡顿;

内容如果存在问题,请各位大佬指正;多谢