目录
1、渲染结果非预期
2、重新渲染时图片闪烁
3、@ObjectLink属性变化UI未更新
上篇文章中我们介绍了LazyForEach的基本使用,展示了如何使用LazyForEach构造一个列表,并演示数据的添加、删除、修改如何与LazyForEach配合并正确的更新UI。本篇将介绍使用LazyForEach的时候会遇到的一些常见问题。
代码如下:
import { SimpleStringDataSource } from './base/LazyForeach';
@Entry
@Component
struct LazyForEachDemo3Page {
private data: SimpleStringDataSource = new SimpleStringDataSource();
aboutToAppear() {
for (let i = 0; i <= 20; i++) {
this.data.pushData(`Hello ${i}`)
}
}
build() {
List({ space: 3 }) {
LazyForEach(this.data, (item: string, index: number) => {
ListItem() {
Row() {
Text(item).fontSize(50)
.onAppear(() => {
console.info("appear:" + item)
})
}.margin({ left: 10, right: 10 })
}
.onClick(() => {
// 点击删除子组件
this.data.deleteData(index);
// 重置所有子组件的index索引
// this.data.reloadData();
})
}, (item: string, index: number) => item + index.toString())
}.cachedCount(5).width('100%').height('100%')
}
}
运行结果如下:
当我们多次点击子组件时,会发现删除的并不一定是我们点击的那个子组件。原因是当我们删除了某一个子组件后,位于该子组件对应的数据项之后的各数据项,其index均应减1,但实际上后续的数据项对应的子组件仍然使用的是最初分配的index,其itemGenerator中的index并没有发生变化,所以删除结果和预期不符。如何修复呢?把注释的那行代码打开即可(在删除一个数据项后调用reloadData方法,重建后面的数据项,以达到更新index索引的目的)。
如下代码只更新文本时,会引起图片的更新,看上去发生了闪烁:
import { StringData } from './base/LazyData';
import { ComplexDataSource } from './base/LazyForeach';
@Entry
@Component
struct LazyRenderDemo4Page {
private moved: number[] = [];
private data: ComplexDataSource = new ComplexDataSource();
aboutToAppear() {
for (let i = 0; i <= 20; i++) {
this.data.pushData(new StringData(`Hello ${i}`, $r('app.media.girl1')));
}
}
build() {
List({ space: 3 }) {
LazyForEach(this.data, (item: StringData, index: number) => {
ListItem() {
Column() {
Text(item.message).fontSize(50)
.onAppear(() => {
console.info("appear:" + item.message)
})
Image(item.imgSrc)
.width(500)
.height(200)
}.margin({ left: 10, right: 10 })
}
.onClick(() => {
item.message += '00';
this.data.reloadData();
})
}, (item: StringData, index: number) => JSON.stringify(item))
}.cachedCount(5).width('100%').height('100%')
}
}
运行效果如下:
更新文本时,图片闪烁
在我们点击ListItem子组件时,我们只改变了数据项的message属性,但是LazyForEach的刷新机制会导致整个ListItem被重建。由于Image组件是异步刷新,所以视觉上图片会发生闪烁。为了解决这种情况我们应该使用@ObjectLink和@Observed去单独刷新使用了item.message的Text组件(代码里还修改了键值对的生成规则)。
import { StringData } from './base/LazyData';
import { ComplexDataSource } from './base/LazyForeach';
@Entry
@Component
struct LazyRenderDemo4Page {
private moved: number[] = [];
private data: ComplexDataSource = new ComplexDataSource();
aboutToAppear() {
for (let i = 0; i <= 20; i++) {
this.data.pushData(new StringData(`Hello ${i}`, $r('app.media.girl1')));
}
}
build() {
List({ space: 3 }) {
LazyForEach(this.data, (item: StringData, index: number) => {
ListItem() {
LazyDemo4ChildComponent({data: item})
}
.onClick(() => {
item.message += '00';
this.data.reloadData();
})
}, (item: StringData, index: number) => {
//key不变,此时只更新相关item的数据
return index.toString()
})
}.cachedCount(5).width('100%').height('100%')
}
}
@Component
struct LazyDemo4ChildComponent {
@ObjectLink data: StringData
build() {
Column() {
Text(this.data.message).fontSize(50)
.onAppear(() => {
console.info("appear:" + this.data.message)
})
Image(this.data.imgSrc)
.width(500)
.height(200)
}.margin({ left: 10, right: 10 })
}
}
修复后运行效果如下:
只更新文本,图片不闪烁
import { NestedString, StringDataV2 } from './base/LazyData';
import { MyDataSource } from './base/LazyForeach';
@Entry
@Component
struct LazyRenderDemo5Page {
@State data: MyDataSource = new MyDataSource();
aboutToAppear() {
for (let i = 0; i <= 20; i++) {
this.data.pushData(new StringDataV2(new NestedString(`Hello ${i}`)));
}
}
build() {
List({ space: 3 }) {
LazyForEach(this.data, (item: StringDataV2, index: number) => {
ListItem() {
LazyRenderDemo5ChildComponent({data: item})
}
.onClick(() => {
// item.message.message += '0';
item.message = new NestedString(item.message.message + '0');
})
}, (item: StringDataV2, index: number) => item.toString() + index.toString())
}.cachedCount(5).width('100%').width('100%')
}
}
@Component
struct LazyRenderDemo5ChildComponent {
@ObjectLink data: StringDataV2
build() {
Row() {
Text(this.data.message.message).fontSize(50)
.onAppear(() => {
console.info("appear:" + this.data.message.message)
})
}.margin({ left: 10, right: 10 })
}
}
@Observed
export class StringDataV2 {
message: NestedString;
constructor(message: NestedString) {
this.message = message;
}
}
@Observed
export class NestedString {
message: string;
constructor(message: string) {
this.message = message;
}
}
@ObjectLink装饰的成员变量仅能监听到其子属性的变化,再深入嵌套的属性便无法观测到了,因此我们只能改变它的子属性去通知对应组件重新渲染,具体请查看@ObjectLink与@Observed的详细使用方法和限制条件。
如何修复呢?既然不支持2层嵌套属性变化,那就在第一场嵌套属性变化上作文章,修复代码如下:
onClick(() => {
item.message = new NestedString(item.message.message + '0');
})
比较简单,就是修改第一层嵌套属性的值(@ObjectLink能监听到该层的变化)。