HarmonyOS鸿蒙Next中@ObservedV2和@Trace修饰渲染二维数组点击不刷新

HarmonyOS鸿蒙Next中@ObservedV2@Trace修饰渲染二维数组点击不刷新

购物车

组件结构

@Entry
@ComponentV2
struct ShoppingCart {
  @Local cartArray: Group[] = [new Group(0, false, [])];
  @Local selectedItems: ChileBean[] = []; // 选中的商品数据源
  @Local isAllSelected: boolean = false;
  @Local allNum: number = 0;
  @Local totalPrice: number = 0;

  aboutToAppear() {
    this.readLocalFileAndParse()
    this.getSelectedChildCount()
  }

  readLocalFileAndParse() {
    let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
    context.resourceManager.getRawFileContent("shoppingCartThree.json", (err: BusinessError, data) => {
      if (err) {
        console.error('getRawFileContent err: ' + JSON.stringify(err))
        return
      }
      let textDecoder = util.TextDecoder.create('utf-8', { ignoreBOM: true })
      let decodedData = textDecoder.decodeToString(data, { stream: false })
      let jsonString = decodedData.toString()
      try {
        let obj = JSON.parse(jsonString) as object;
        let array = obj['list'] as Group[]
        console.log('文件解析   -------   ' + JSON.stringify(array))

        let newArray: Group[] = []
        array.map((item: Group) => {
          let newCart = new Group(item.id, item.check, []) // 重新赋值
          newCart.name = item.name
          newCart.childList = item.childList
          newArray.push(newCart)
        })
        this.cartArray = newArray
      } catch (err) {
        console.error('parse err: ' + JSON.stringify(err))
      }
    })
  }

  getSelectedChildCount() {
    let allNum: number = 0;
    let totalPrice: number = 0;
    this.cartArray.forEach((item) => {
      (item.childList ?? []).forEach((child) => {
        if (child.child_check) {
          allNum += child.num;
          totalPrice += (child.couponPrice ?? 0) * child.num;
        }
        if (child.child_check) {
          this.selectedItems.push(child)
        }
      });
    })
    console.log('selectedItems-----------------------------' + JSON.stringify(this.selectedItems))
    this.allNum = allNum;
    this.totalPrice = Math.trunc(totalPrice * 100) / 100;
  }

  @Builder
  createBottom() {
    Row() {
      Row() {
        Text('全选')
      }.padding(12)
      .backgroundColor(this.isAllSelected ? Color.Orange : '#eee')
      .onClick(() => {
        this.isAllSelected = !this.isAllSelected;
        this.cartArray.forEach(group => {
          group.check = this.isAllSelected;
          let childList = group.childList ?? [];
          childList.forEach((item) => {
            item.child_check = this.isAllSelected;
          });
        });
        this.getSelectedChildCount()
      })

      Column() {
        Row({ space: 12 }) {
          if (this.allNum > 0) {
            Text("已选" + this.allNum)
              .fontSize(14)
              .fontColor("#70111111")
          }
          Text() {
            Span("合计:")
              .fontSize(16)
            Span("¥")
              .fontSize(14)
              .fontColor(Color.Orange)
              .fontWeight(FontWeight.Bold)
            Span(String(this.totalPrice))
              .fontSize(20)
              .fontColor(Color.Orange)
              .fontWeight(FontWeight.Bold)
          }
          .margin({ right: 10 })
        }
      }

      Text("提交")
        .border({ radius: 20 })
        .width(90)
        .textAlign(TextAlign.Center)
        .fontColor(Color.White)
        .backgroundColor(this.allNum > 0 ? "#ffe5570b" : "#ffc3bdbd")
        .height(45)
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .onClick(() => {
          if (this.allNum > 0) {
            // showToast("提交数据源"+JSON.stringify(this.cartArray))
          } else {
            // showToast("未选择商品")
          }
        })
        .margin({ right: 10 })
    }
    .width('100%')
    .justifyContent(FlexAlign.SpaceBetween)
    .padding({ left: 10, right: 10 })
  }

  build() {
    Column() {
      PageTitle({
        label: '购物车',
        type: 'Sub',
        titleFontColor: 'black',
        backArrowsColor: 'black',
        backBtnColor: 'white',
        backBtnClickCallback: () => {
          router.back()
        }
      })
      List({ space: 10 }) {
        ForEach(this.cartArray, (item: Group, index: number) => {
          ListItem() {
            CartHeaderWidget({item:item, getSelectedChildCount:() => {
              this.isAllSelected = this.cartArray.every(group => group.check);                 // 是否全选判断
            }})
          }
        }, (item: Group, index: number) => JSON.stringify(item))
      }.layoutWeight(1)

      this.createBottom()
    }.padding({ left: 10, right: 10, bottom: 10 })
    .height("100%")
    .width("100%")
  }
}

@ComponentV2
struct CartHeaderWidget {
  @Param item: Group = new Group(0, false, [])
  @Event getSelectedChildCount?: () => void

  build() {
    Column() {
      Row() {
        Row() {
          Text('header')
        }.padding(12)
        .backgroundColor(this.item.check ? Color.Orange : '#eee')
        .onClick(() => {
          this.item.check = !this.item.check
          let childList = this.item.childList ?? [];
          childList.forEach((child) => {
            child.child_check = this.item.check;
          })
          if (this.getSelectedChildCount) {
            this.getSelectedChildCount()
          }
        })


        Text(this.item.name)
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .fontColor('#111')
          .layoutWeight(1)
      }

      List() {
        ForEach(this.item.childList, (child: ChileBean, child_index: number) => {
          ListItem() {
            CartRowWidget({
              child:child,
              getSelectedChildCount: () => {

                this.item.check = (this.item.childList ?? []).every(everyChild => everyChild.child_check)
                if (this.getSelectedChildCount) {
                  this.getSelectedChildCount()
                }
              }
            })
          }
        },(child: ChileBean) => JSON.stringify(child))
      }
    }
  }
}

@ComponentV2
struct CartRowWidget {
  @Param @Once child: ChileBean = new ChileBean(0, false, 0)
  @Event getSelectedChildCount?: () => void

  build() {
    Column() {
      Row({ space: 12 }) {
        Checkbox({ name: this.child.name ?? '' })
          .selectedColor('#00BFA5')
          .shape(CheckBoxShape.ROUNDED_SQUARE)
          .unselectedColor('#00BFA5')
          .select(this.child.child_check )
          .onChange((value: boolean) => {
            // 记录当前Checkbox的选中状态
            this.child.child_check = value
            if (this.getSelectedChildCount) {
              this.getSelectedChildCount()
            }
          })
          .width(20)
          .height(20)

        Image(this.child.img)
          .height(90)
          .width(90)

        Column({ space: 10 }) {
          Text(this.child.name)
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .fontColor('#111')

          Row({ space: 12 }) {
            Text() {
              Span('¥')
                .fontSize(14)
                .fontWeight(FontWeight.Bold)
                .fontColor("#ffe5570b")

              Span("" + this.child.couponPrice)
                .fontSize(20)
                .fontWeight(FontWeight.Bold)
                .fontColor("#ffe5570b")
            }

            ReduceWidget({child:this.child, getSelectedChildCount:this.getSelectedChildCount})
          }
        }
        .margin({ left: 10 })
        .alignItems(HorizontalAlign.Start)

      }.width('100%')
      .padding({ left: 10, right: 20 })
      .alignItems(VerticalAlign.Center)
      .width('100%')
    }

  }
}

@ComponentV2
struct ReduceWidget {
  @Param @Once child: ChileBean = new ChileBean(0, false, 0)
  @Event getSelectedChildCount?: () => void

  build() {
    Row({ space: 7 }) {
      Text("-")
        .border({ width: 1, color: Color.Gray, radius: 5 })
        .width(30)
        .height(20)
        .textAlign(TextAlign.Center)
        .onClick(() => {
          if (this.child.num == 1) {
            promptAction.showToast({ message: '该宝贝不能减少了!' })
          } else {
            this.child.num--
          }
          if (this.getSelectedChildCount) {
            this.getSelectedChildCount()
          }
        })
      Text(`${this.child.num}`)
        .padding({ left: 3, right: 3 })
      Text("+")
        .border({ width: 1, color: Color.Gray, radius: 5 })
        .width(30)
        .height(20)
        .textAlign(TextAlign.Center)
        .onClick(() => {
          this.child.num++
          if (this.getSelectedChildCount) {
            this.getSelectedChildCount()
          }
        })
    }
  }
}

[@ObservedV2](/user/ObservedV2)
export class Group {
  id: number = 0
  name?: string = ''
  [@Trace](/user/Trace) check: boolean = false
  [@Trace](/user/Trace) childList?: ChileBean[] = []
  constructor(id: number, check: boolean,childList: ChileBean[] ) {
    this.id = id
    this.check = check
    this.childList = childList
  }
}

[@ObservedV2](/user/ObservedV2)
export class ChileBean {
  childId: number = 0
  name?: string  //商品名称
  img?: string  //商品图
  couponPrice?: number //券后价
  [@Trace](/user/Trace) child_check: boolean = false //选中状态
  [@Trace](/user/Trace) num: number = 0 //数量
  constructor(childId: number, child_check: boolean,num:number) {
    this.childId = childId
    this.child_check = child_check
    this.num = num
  }
}

JSON 数据

{
  "list": [
    {
      "id": 131,
      "goods_default_icon": "https://isoftstone-metaservice-sit.obs.cn-north-4.myhuaweicloud.com:443/o2o%2F6917569484028618252%2FDEFAULT%2F8cbc1047f95d444cb7b3f02d7438141a.png",
      "pos": "1",
      "name": "婴儿护理用品专卖",
      "check": false,
      "childList": [
        {
          "childId": 1,
          "child_pos": 333,
          "goodsid": "508",
          "num": 1,
          "name": "新生儿尿布2条装",
          "marketid": "0132",
          "img": "https://tse3-mm.cn.bing.net/th/id/OIP-C.0OLkB4kpqaftDNKhgjOvfQHaE7?w=285&h=190&c=7&r=0&o=5&dpr=2&pid=1.7",
          "price": 30.00,
          "couponPrice": 28.00,
          "child_check": false
        },
        {
          "childId": 2,
          "child_pos": 222,
          "goodsid": "506",
          "num": 1,
          "name": "新生儿纯棉洗澡起泡擦",
          "marketid": "0131",
          "img": "https://tse4-mm.cn.bing.net/th/id/OIP-C.2fzOsOjmQAfnu6hVBLSw5gAAAA?w=231&h=180&c=7&r=0&o=5&dpr=2&pid=1.7",
          "price": 30.00,
          "couponPrice": 42.00,
          "child_check": false
        }
      ]
    },
    {
      "id": 133,
      "goods_default_icon": "https://isoftstone-metaservice-sit.obs.cn-north-4.myhuaweicloud.com:443/o2o%2F6917569484028618252%2FDEFAULT%2Ff0f7aa35a6d24cec8de5e7eb3088a33e.png",
      "pos": "221",
      "name": "新风手机专卖店",
      "check": false,
      "childList": [
        {
          "childId": 3,
          "child_pos": 415,
          "goodsid": "508",
          "num": 1,
          "name": "新生儿尿布2条装",
          "marketid": "0131",
          "img": "https://tse3-mm.cn.bing.net/th/id/OIP-C.0OLkB4kpqaftDNKhgjOvfQHaE7?w=285&h=190&c=7&r=0&o=5&dpr=2&pid=1.7",
          "price": 48.00,
          "couponPrice": 39.00,
          "child_check": false
        },
        {
          "childId": 4,
          "child_pos": 414,
          "goodsid": "821",
          "num": 1,
          "name": "新生儿纯棉洗澡起泡擦",
          "marketid": "0132",
          "img": "https://tse4-mm.cn.bing.net/th/id/OIP-C.2fzOsOjmQAfnu6hVBLSw5gAAAA?w=231&h=180&c=7&r=0&o=5&dpr=2&pid=1.7",
          "price": 98.00,
          "couponPrice": 62.00,
          "child_check": false
        }
      ]
    }
  ]
}

更多关于HarmonyOS鸿蒙Next中@ObservedV2和@Trace修饰渲染二维数组点击不刷新的实战教程也可以访问 https://www.itying.com/category-93-b0.html

5 回复

更多关于HarmonyOS鸿蒙Next中@ObservedV2和@Trace修饰渲染二维数组点击不刷新的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


你先了解一下:

@Param [@Once](/user/Once)

官方都有说明:@Once 仅从外部初始化一次且不接受后续同步变化,你还指望他改变?

你把@Param @Once删除试试,

在HarmonyOS鸿蒙Next中,@ObservedV2@Trace修饰二维数组点击不刷新时,需检查以下几点:

  1. 确保二维数组每个元素都使用@ObservedV2修饰
  2. @Trace需正确绑定到触发更新的方法上
  3. 数组变更需通过set方法触发响应式更新
  4. 检查是否在自定义组件中正确使用了@Component装饰器

若仍不刷新,可尝试将二维数组改为对象数组,或使用@Track单独标记需要跟踪的属性。数据变更必须通过赋值新数组或对象的方式触发更新。

在HarmonyOS Next中,@ObservedV2@Trace修饰的二维数组点击不刷新的问题,通常是由于数据变更未正确触发UI更新。从代码来看,需要注意以下几点:

  1. 确保所有需要响应式更新的属性都正确使用了@Trace修饰。在您的代码中,Group和ChileBean类的check、child_check、num等属性已正确添加@Trace

  2. 对于嵌套数组(childList),虽然数组本身被@Trace修饰,但直接修改数组元素可能不会触发更新。建议在修改数组元素后,重新赋值整个数组:

    // 修改childList中的元素后
    this.item.childList = [...this.item.childList];
    
  3. 检查CartHeaderWidget组件中的参数传递方式。当前使用@Param传递item对象,确保在父组件中修改item属性后,子组件能接收到更新。

  4. 在ReduceWidget组件中,虽然child参数使用了@Once修饰,但实际需要响应式更新,建议移除@Once修饰符。

  5. 确保所有状态变更后都调用了getSelectedChildCount回调来触发重新计算。

这些修改应该能解决二维数组点击不刷新的问题。

回到顶部