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

cke_2702.png

虚拟机版本:HarmonyOS 5.0.5(17)

bug页面如下:可以看到此时【全选复选框勾上,但上面的框没有勾,正常应该都勾上】

cke_3493.png

代码:

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

10 回复

【背景知识】
[@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的原因,而不是绕开它改用另一种写法,感谢。

您好,这个并不是bug,而是@State监听的规格如此,数组项中属性的赋值观察不到。

观察变化一般可以使用Observed或者ObservedV2去监听属性变化,从而触发UI刷新。

如果不按照我的复现步骤来操作,直接打开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

  ); // 通过映射生成新数组

})

你好,首先感谢您的回复。

1、对于你说的【task.selected = this.selectedAll会导致 对象引用未变更。由于ArkUI的响应式系统依赖对象引用来判断是否需要更新UI,当对象引用不变时,ForEach不会触发子项重新渲染(体现在onAppear未被调用)】

请问该如何解释多次点击全选按钮可以正常切换所有复选框的勾/不勾这种情况呢?

关于第二点也是类似:【你通过装饰器@Extend(Checkbox)绑定样式时,.select(selected)属于静态属性设置,无法响应后续状态变化。这导致即使数据层task.selected已更新,Checkbox视觉状态仍滞后】

无法解释为何大部分情况下这个复选框是正常工作的,盼复,十分感谢,

有要学HarmonyOS AI的同学吗,联系我:https://www.itying.com/goods-1206.html

你好,问题原因已找到,是上门【星纪】网友提到的forEach的key生成策略导致的,详情可以查看上面那条评论,

在HarmonyOS Next中,ListItem的渲染基于ArkUI声明式开发范式,使用组件复用机制优化性能。其生命周期通过aboutToAppear和aboutToDisappear回调管理,分别对应组件创建和销毁阶段。列表滚动时通过LazyForEach实现按需加载,避免不必要的渲染开销。状态变化通过@State@Prop驱动UI更新。

问题出在数据更新的方式上。在ListItem中,你直接修改了task.selected,但task是ForEach遍历的数组元素,这种修改不会触发ArkUI的响应式更新。

具体分析:

  1. 在单个Checkbox的onChange中,你直接修改了task.selected = select,这个修改没有通过@State装饰的taskList进行,ArkUI无法感知数据变化
  2. 虽然你调用了taskListChange()方法,但taskList本身引用没有改变,ListItem不会重新渲染
  3. 全选操作中你正确使用了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会正确重新渲染。

回到顶部