针对列表的优化常见方案###
在使用列表的时候,有时在列表比较复杂时,会出现滑动卡顿情况。那么针对列表优化整理了一下几个简单方案,分为复用机制,懒加载机制,冻结功能配置,设置指定的缓存区大小,减少条目的布局节点数;
针对优化的方案版本代码是基于 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 树的节点个数,如果节点个数太多,绘制时间 就会变长,最终导致丢帧 引发卡顿;
内容如果存在问题,请各位大佬指正;多谢