文档开发者中心
我的
|

ArkUI (方舟UI框架)

HarmonyOS 5

求指点:关于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%')

  }
}
点赞
2
收藏
回复
12
分享
举报
浏览346 编辑于2025-09-06 16:01广东
全部评论
最多点赞
最新发布
最早发布
|
采纳答复

背景知识】

  • ForEach接口基于数组类型数据来进行循环渲染,需要与容器组件配合使用,且接口返回的组件应当是允许包含在ForEach父容器组件中的子组件。
  • 被@State修饰的变量可以触发其直接绑定UI组件的刷新,当状态改变即数据改变时,UI会发生对应的渲染改变,但并不是所有被@State修饰的变量都会引起UI的刷新,只有被框架观察到的修改才会引起UI刷新。

【解决方案】

ForEach使用了默认的键值生成函数为,全选的时候,key没有变,导致渲染出现了问题,参考数据变化不渲染。可以自定义keyGenerator函数,比如, (item: TaskItem) => Math.random().toString()。

3楼编辑于2025-09-18 03:34 来自广东

【背景知识】
@ObservedV2装饰器与@Trace装饰器用于装饰类以及类中的属性,使得被装饰的类和属性具有深度观测的能力。

【解决方案】
使用@ObservedV2装饰类,用@Trace装饰存储数据源的变量,即可实现在数据源发生变化时同步刷新页面。

修改TaskItem为class,然后通过ObservedV2观测数据变化。

收起
自动换行
深色代码主题
复制
···
@ObservedV2
class TaskItem {
  title: string
  @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)];
···

7
2楼回复于2025-09-09 09:40 来自广东
  • 须臾
    你好,首先感谢您的回复。 我是希望找到我写的代码出bug的原因,而不是绕开它改用另一种写法,感谢。
    2025-09-09 17:36 来自广东
  • 旺旺孙冰冰回复须臾
    您好,这个并不是bug,而是@State监听的规格如此,数组项中属性的赋值观察不到。 https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-state#观察变化 一般可以使用Observed或者ObservedV2去监听属性变化,从而触发UI刷新。
    2025-09-10 02:32 来自广东
  • 须臾回复旺旺孙冰冰
    如果不按照我的复现步骤来操作,直接打开app就点全选,是可以正常修改上面的选中/不选中的,请问这个现象怎么解释呢?
    2025-09-10 09:41 来自广东
  • 展开剩余4条回复

你的全选功能失效问题与 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

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

})
展开全部
2
1楼回复于2025-09-08 09:46 来自河北
  • 须臾
    你好,首先感谢您的回复。 1、对于你说的【task.selected = this.selectedAll会导致 对象引用未变更。由于ArkUI的响应式系统依赖对象引用来判断是否需要更新UI,当对象引用不变时,ForEach不会触发子项重新渲染(体现在onAppear未被调用)】 请问该如何解释多次点击全选按钮可以正常切换所有复选框的勾/不勾这种情况呢? 关于第二点也是类似:【你通过装饰器@Extend(Checkbox)绑定样式时,.select(selected)属于静态属性设置,无法响应后续状态变化。这导致即使数据层task.selected已更新,Checkbox视觉状态仍滞后】 无法解释为何大部分情况下这个复选框是正常工作的,盼复,十分感谢
    2025-09-09 17:35 来自广东
  • 须臾
    你好,问题原因已找到,是上门【星纪】网友提到的forEach的key生成策略导致的,详情可以查看上面那条评论
    2025-09-13 02:44 来自广东
写回答
新增插入模板功能
一键使用模板,快速填写内容,轻松发帖~
知道了
  • 为了保障您的信息安全,请勿上传您的敏感个人信息(如您的密码等信息)和您的敏感资产信息(如关键源代码、签名私钥、调试安装包、业务日志等信息),且您需自行承担由此产生的信息泄露等安全风险。
  • 如您发布的内容为转载内容,请注明内容来源。

我要提问题

了解社区公约,与您携手共创和谐专业的开发者社区。