HarmonyOS 鸿蒙Next中求指点:关于ListItem的渲染和生命周期的疑问
HarmonyOS 鸿蒙Next中求指点:关于ListItem的渲染和生命周期的疑问 本人初学鸿蒙,最近写了一个简单页面,有个bug百思不得其解,甚至有时候不能稳定触发,感觉涉及到ListItem的渲染时机,发出来请各位大佬指点。
代码贴在最后
复现步骤:
第一步:手动勾选【敲代码1】【敲代码2】【敲代码3】后,【全选】复选框会自动勾上,到此正常。
第二步:手动取消【敲代码2】【敲代码3】(任意一个或两个),此时 【全选】复选框会取消勾选,到此还正常
第三步(bug):此时,再勾选【全选】复选框,上面的【敲代码1】【敲代码2】【敲代码3】没有勾上,还保留第二步的状态,这是为什么?
备注:如第一次未触发,可重复操作几次,触发率非常高。
另:ListItem中,我借助onAppear方法,发现bug触发的时候,onAppear方法没有被调用,也就是说ListItem没有重新渲染。技术有限,只能排查到这里,希望给各位大神提供一些灵感。
还有就是:我知道代码还有可优化的地方,复选框也有更好的实现方式,例如checkBoxGroup等,但我想知道的是我这么写的问题在哪?关于代码优化的建议在此就谢绝各位大大啦~~十分感谢
使用工具:DevEco Studio 5.0.5 Release
虚拟机版本:HarmonyOS 5.0.5(17)
bug页面如下:可以看到此时【全选复选框勾上,但上面的框没有勾,正常应该都勾上】
代码:
import { JSON } from '@kit.ArkTS';
interface TaskItem {
title: string
selected: boolean
}
@Extend(Checkbox)
function checkBoxStyle(selected: boolean) {
.width(20)
.height(20) // 方形的checkBox
.shape(CheckBoxShape.ROUNDED_SQUARE) // 选中的颜色
.selectedColor(Color.Orange)
.select(selected)
}
@Entry
@Component
struct TodoListDemo {
@State taskText: string = '';
@State @Watch('taskListChange') taskList: Array<TaskItem> = [
{
title: '敲代码1',
selected: false
},
{
title: '敲代码2',
selected: false
},
{
title: '敲代码3',
selected: false
},
];
@State selectedAll: boolean = false;
@State completed: number = 0;
@State unfinished: number = this.taskList.length;
taskListChange() {
this.completed = this.taskList.filter(item => item.selected === true).length
this.unfinished = this.taskList.length - this.completed
this.selectedAll = this.unfinished === 0
console.log('selectedAll:' + this.selectedAll)
console.log('list:' + JSON.stringify(this.taskList))
}
build() {
Column({ space: 10 }) {
TextInput({ placeholder: '请输入任务后,按回车确定', text: this.taskText })
.fontColor(Color.Gray)
.onSubmit((type, event) => {
this.taskList.push({
title: event.text,
selected: false
})
})
List() {
ForEach(this.taskList, (task: TaskItem, index: number) => {
ListItem() {
Row() {
// Checkbox()
// .checkBoxStyle(task.selected)
// .onChange(select => {
// this.taskList = this.taskList.map((item, _index) => {
// _index === index && (item.selected = select)
// return item
// })
// })
Checkbox()
.checkBoxStyle(task.selected)
.onChange(select => {
task.selected = select
this.taskListChange()
})
Text(task.title)
.fontSize(20)
.width('100%')
}
.width('100%')
.justifyContent(FlexAlign.Center)
.alignItems(VerticalAlign.Center)
.padding(20)
}
.onAppear(()=>{
console.log("onappear");
})
.border({
width: 1
})
})
}
Row() {
Row() {
Checkbox()
.checkBoxStyle(this.selectedAll)
.onClick(() => {
this.selectedAll = !this.selectedAll
this.taskList = this.taskList.map((task) => {
task.selected = this.selectedAll
return task
})
})
Text() {
Span('全选 已完成' + `${this.completed}`)
Span(',未完成' + `${this.unfinished}`)
}
}
Button('清除已完成任务')
.backgroundColor(Color.Red)
.fontColor(Color.White)
}
.width('100%')
.justifyContent(FlexAlign.SpaceAround)
}
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
.width('100%')
.height('100%')
}
}
更多关于HarmonyOS 鸿蒙Next中求指点:关于ListItem的渲染和生命周期的疑问的实战教程也可以访问 https://www.itying.com/category-93-b0.html
【背景知识】
[@ObservedV2装饰器与@Trace装饰器](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-new-observedv2-and-trace)用于装饰类以及类中的属性,使得被装饰的类和属性具有深度观测的能力。
【解决方案】
使用@ObservedV2装饰类,用@Trace装饰存储数据源的变量,即可实现在数据源发生变化时同步刷新页面。
修改TaskItem为class,然后通过ObservedV2观测数据变化。
[@ObservedV2](/user/ObservedV2)
class TaskItem {
title: string
[@Trace](/user/Trace) selected: boolean
constructor(title: string, selected: boolean) {
this.title = title;
this.selected = selected;
}
}
@State @Watch('taskListChange') taskList: Array<TaskItem> = [new TaskItem("敲代码1",false),new TaskItem("敲代码2",false),new TaskItem("敲代码3",false)];
更多关于HarmonyOS 鸿蒙Next中求指点:关于ListItem的渲染和生命周期的疑问的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
你好,首先感谢您的回复。
我是希望找到我写的代码出bug的原因,而不是绕开它改用另一种写法,感谢。
如果不按照我的复现步骤来操作,直接打开app就点全选,是可以正常修改上面的选中/不选中的,请问这个现象怎么解释呢?
你的全选功能失效问题与 ArkUI的响应式更新机制 和 对象引用不变性 相关:在onClick全选事件中,直接修改task.selected = this.selectedAll会导致 对象引用未变更。由于ArkUI的响应式系统依赖对象引用来判断是否需要更新UI,当对象引用不变时,ForEach不会触发子项重新渲染(体现在onAppear未被调用);你通过装饰器@Extend(Checkbox)绑定样式时,.select(selected)属于静态属性设置,无法响应后续状态变化。这导致即使数据层task.selected已更新,Checkbox视觉状态仍滞后
修复
1/使用不可变数据更新逻辑:将全选操作修改为 创建新对象而非修改原对象
// 修改全选Checkbox的点击事件
.onClick(() => {
const newSelectedAll = !this.selectedAll;
this.taskList = this.taskList.map(task => ({
...task,
selected: newSelectedAll // 创建新对象而非修改原属性
}));
this.selectedAll = newSelectedAll; // 保持状态同步
})
2/修正子项Checkbox的事件处理
// 修改子项Checkbox的onChange事件
.onChange(select => {
this.taskList = this.taskList.map((item, idx) =>
idx === index ? { ...item, selected: select } : item
); // 通过映射生成新数组
})
有要学HarmonyOS AI的同学吗,联系我:https://www.itying.com/goods-1206.html
你好,问题原因已找到,是上门【星纪】网友提到的forEach的key生成策略导致的,详情可以查看上面那条评论,
问题出在数据更新的方式上。在ListItem中,你直接修改了task.selected,但task是ForEach遍历的数组元素,这种修改不会触发ArkUI的响应式更新。
具体分析:
- 在单个Checkbox的onChange中,你直接修改了task.selected = select,这个修改没有通过@State装饰的taskList进行,ArkUI无法感知数据变化
- 虽然你调用了taskListChange()方法,但taskList本身引用没有改变,ListItem不会重新渲染
- 全选操作中你正确使用了this.taskList = this.taskList.map(…),这会触发重新渲染
解决方案: 将单个Checkbox的onChange改为:
.onChange(select => {
this.taskList = this.taskList.map((item, i) => {
return i === index ? {...item, selected: select} : item
})
})
这样通过修改taskList的引用来触发ArkUI的响应式更新,ListItem会正确重新渲染。