10.23 @Observed深层监听

对象数组不能深层监听

效果图:

![[738c8011-f779-48ab-9f4d-791a59055f7b.png]]

代码

下面的代码,在修改计数器数量时,实际数据已经发生变化,但因为没有触发视图更新,页面上数量不会改变

TypeScript 复制代码
// 购物车中商品总价的计算
interface CartModel {
  readonly cart_id: number,
  productName: string,
  imgUrl: string,
  price: number,
  count: number,
  isSelected: boolean
}

@Entry
@Component
struct Index {
  @State total: number = 0
  @State cartData: CartModel[] = [
    {
      cart_id: 1,
      productName: "华为平板",
      price: 3000,
      count: 2,
      isSelected: true,
      imgUrl: 'https://img01.hua.com/uploadpic/newpic/9012757.jpg_220x240.jpg'
    },
    {
      cart_id: 3,
      productName: "华为手机",
      price: 7000,
      count: 3,
      isSelected: true,
      imgUrl: 'https://img01.hua.com/uploadpic/newpic/9012754.jpg_220x240.jpg'
    }
  ]

  build() {
    Column() {
      // 1. 标题
      Text("购物车")
        .fontSize(30)
        .fontColor(Color.White)
        .margin(20)

      // 2.商品
      ForEach(this.cartData,(item:CartModel,index: number)=>{
        Row() {
          // 商品名称
          Text(item.productName)
          // 商品图片
          Image(item.imgUrl)
            .width(100)
            .height(100)
            .margin(10)
            .borderRadius(10)
          // 商品数量
          Counter() {
            Text(item.count.toString())
          }
          .margin(5)
          // 增加数量
          .onInc(() => {
            item.count++
            console.log(JSON.stringify(this.cartData))
          })
          // 减少数量
          .onDec(() => {
            item.count--
            console.log(JSON.stringify(this.cartData))
          })
        }
        .width('95%')
        .height(150)
        .backgroundColor(Color.White)
        .borderRadius(8)
        .margin(5)
      }, (item: CartModel)=>item.cart_id.toString())

    }
    .width('100%')
    .height('100%')
    .backgroundColor(Color.Gray)
  }
}

用数组的splice方法解决

这种方案不能直接使用ForEach中的Item,要直接操作数组

代码改动部分

![[b5f958fd-065c-431b-8891-b6c85eeccb49.png]]

完整代码

TypeScript 复制代码
// 购物车中商品总价的计算
interface CartModel {
  readonly cart_id: number,
  productName: string,
  imgUrl: string,
  price: number,
  count: number,
  isSelected: boolean
}

@Entry
@Component
struct Index {
  @State total: number = 0
  @State cartData: CartModel[] = [
    {
      cart_id: 1,
      productName: "华为平板",
      price: 3000,
      count: 2,
      isSelected: true,
      imgUrl: 'https://img01.hua.com/uploadpic/newpic/9012757.jpg_220x240.jpg'
    },
    {
      cart_id: 3,
      productName: "华为手机",
      price: 7000,
      count: 3,
      isSelected: true,
      imgUrl: 'https://img01.hua.com/uploadpic/newpic/9012754.jpg_220x240.jpg'
    }
  ]

  build() {
    Column() {
      // 1. 标题
      Text("购物车")
        .fontSize(30)
        .fontColor(Color.White)
        .margin(20)

      // 2.商品
      ForEach(this.cartData,(item:CartModel,index: number)=>{
        Row() {
          // 商品名称
          Text(item.productName)
          // 商品图片
          Image(item.imgUrl)
            .width(100)
            .height(100)
            .margin(10)
            .borderRadius(10)
          // 商品数量
          Counter() {
            Text(this.cartData[index].count.toString())
          }
          .margin(5)
          // 增加数量
          .onInc(() => {
            this.cartData[index].count++
            this.cartData.splice(index,1,this.cartData[index])
          })
          // 减少数量
          .onDec(() => {
            this.cartData[index].count--
            this.cartData.splice(index,1,this.cartData[index])
          })
        }
        .width('95%')
        .height(150)
        .backgroundColor(Color.White)
        .borderRadius(8)
        .margin(5)
      }, (item: CartModel)=>item.cart_id.toString())

    }
    .width('100%')
    .height('100%')
    .backgroundColor(Color.Gray)
  }
}

用@Observed装饰器和@ObjectLink装饰器实现

https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-observed-and-objectlink-V5

3.1 为什么要使用@Observed装饰器

@State、@Prop、@Link、@Provide和@Consume装饰器仅能观察到第一层的变化,但是在实际应用开发中,应用会根据开发需要,封装自己的数据模型。对于多层嵌套的情况,比如二维数组,或者数组项class,或者class的属性是class,他们的第二层的属性变化是无法观察到的。这就引出了@Observed/@ObjectLink装饰器。

@Observed/@ObjectLink配套使用是用于嵌套场景的观察,主要是为了弥补装饰器仅能观察一层的能力限制,

3.2 使用@Observe装饰器的注意事项

@ObjectLink和@Observed类装饰器用于在涉及嵌套对象或数组的场景中进行双向数据同步:

  • 使用new创建被@Observed装饰的类,可以被观察到属性的变化;

  • 子组件中@ObjectLink装饰器装饰的状态变量用于接收@Observed装饰的类的实例,和父组件中对应的状态变量建立双向数据绑定。这个实例可以是数组中的被@Observed装饰的项,或者是class object中的属性,这个属性同样也需要被@Observed装饰。

  • @Observed用于嵌套类场景中,观察对象类属性变化,要配合自定义组件使用(示例详见嵌套对象),如果要做数据双/单向同步,需要搭配@ObjectLink使用

3.3 完整代码

注意事项:

  1. 被观察的对象必须是Class类型(参考代码第11行)

  2. 被观察对象要添加@Observe装饰器 (参考代码第10行)

  3. 对象数组的每一项由class实例组成 (参考代码第33行)

  4. 必须由父组件将Class实例数据传入自定义子组件使用 (参考代码第63行)

  5. 子组件中用@ObserveLink来接收需要观察的数据 (参考代码第75行)

TypeScript 复制代码
interface CartModel {
  readonly cart_id: number
  productName: string
  price: number
  count: number
  isSelected: boolean
  imgUrl: string
}

@Observed
class CartClass {
  readonly cart_id: number
  productName: string
  price: number
  count: number
  isSelected: boolean
  imgUrl: string

  constructor(cart: CartModel) {
    this.cart_id = cart.cart_id
    this.productName = cart.productName
    this.price = cart.price
    this.count = cart.count
    this.isSelected = cart.isSelected
    this.imgUrl = cart.imgUrl
  }
}

/**
 * {
 cart_id: 1,
 productName: "华为平板",
 price: 30,
 count: 2,
 isSelected: true,
 imgUrl: 'https://img01.hua.com/uploadpic/newpic/9012757.jpg_220x240.jpg'
 }
 */
@Entry
@Component
struct Index {
  @State cartData: CartClass[] = [
    new CartClass({
      cart_id: 1,
      productName: "华为平板1",
      price: 30,
      count: 2,
      isSelected: true,
      imgUrl: 'https://img01.hua.com/uploadpic/newpic/9012757.jpg_220x240.jpg'
    }),
    new CartClass({
      cart_id: 2,
      productName: "华为平板2",
      price: 130,
      count: 5,
      isSelected: true,
      imgUrl: 'https://img01.hua.com/uploadpic/newpic/9012757.jpg_220x240.jpg'
    })
  ]
  @State total: number = 0

  // computeTotal() {
  //   let num = 0
  //   // for (let i = 0; i < this.cartData.length; i++) {
  //   //   num += this.cartData[i].count * this.cartData[i].price
  //   // }
  //
  //   for (let item of this.cartData) {
  //     num += item.count * item.price
  //   }
  //   this.total = num
  // }

  // aboutToAppear(): void {
  //   this.computeTotal()
  // }

  build() {
    Column() {
      //1. 标题
      Text("购物车")
        .fontSize(30)
      //2. 列表
      ForEach(this.cartData, (item: CartModel) => {
        CartItem({ item: item, total: this.total })
      }, (item: CartModel) => item.cart_id.toString())

      // 3. 总价
      Text(`总价:${this.total}`)
    }
    .width('100%')
    .height('100%')
    .backgroundColor(Color.Gray)
  }
}

@Component
struct CartItem {
  @ObjectLink item: CartClass
  @Link total: number

  aboutToAppear(): void {
    this.total += this.item.count * this.item.price
  }

  build() {
    Row() {
      Text(this.item.cart_id.toString())
      Image(this.item.imgUrl)
        .width(30)
        .height(30)
      Counter() {
        Text(this.item.count.toString())
      }
      .onInc(() => {
        this.item.count++
        this.total += this.item.price
      })
      .onDec(() => {
        this.item.count--
        this.total -= this.item.price
      })

      Text(`合计:${this.item.count * this.item.price}`)
    }
    .width('100%')
    .height(70)
    .margin(10)
    .backgroundColor(Color.White)
  }
}

为什么@observe修饰的对象变化后,UI没有刷新

https://developer.huawei.com/consumer/cn/doc/harmonyos-faqs-V5/faqs-arkui-256-V5

![[80eeb9db-1ac8-4577-94d4-7bc8b80312b3.png]]

浅拷贝和深拷贝的理解

案例1:

TypeScript 复制代码
interface ItemModel {
  id: number,
  name: string
}


let arr: ItemModel[] = [
  {
    id: 1,
    name: 'a1'
  },
  {
    id: 1,
    name: 'a2'
  }
]
let arr1: ItemModel[] = []
arr1 = arr // 浅拷贝
arr1[1].name = 'zz'
// console.log(arr[1].name); // zz


let b = 1
let a = b

let numList = [6, 8, 9]
//1. 浅拷贝: 拷贝地址,一个改变,另一个也改变
// let newList: number[] = numList
//2. 深拷贝一: 拷贝值,一个改变,不影响另一个
// let newList: number[] = []
// newList[0] = numList[0]
// newList[1] = numList[1]
// newList[2] = numList[2]

// 3. 深拷贝二:
// let newList: number[] = []
// // ...是ES6中的扩展运算符,相当于把numList中的每个值拿出来,放进newList中
// newList = [...numList]

// 4. 深拷贝三:
let newList: number[] = []
//JSON.stringify 把JSON对象转换为字符串  [] {}   [{},{}] 前面这三种类型的数据都是JSON对象
// JSON.parse 把符合json格式的字符串转换为json对象  "{'name', 'a'}"
newList = JSON.parse(JSON.stringify(numList))

newList[2] = 100
console.log(numList.toString());
console.log(newList.toString());


@Entry
@Component
struct Index {
  build() {
    Button('改变')
      .onClick(() => {

      })
  }
}

案例2

TypeScript 复制代码
interface ItemModel {
  id: number,
  name: string
}

// [地址1, 地址2]
let arr1: ItemModel[] = [
  {
    id: 1,
    name: 'a1'
  },
  {
    id: 1,
    name: 'a2'
  }
]
let arr2: ItemModel[] = []
// arr2 = arr1 // 浅拷贝
arr2[0] = arr1[0]
arr2[1] = arr1[1]
// arr2.push({
//   id: 3,
//   name: 'a3'
// })
arr2[1].name = '小米'
console.log(JSON.stringify(arr1)); // '[{"id":1,"name":"a1"},{"id":1,"name":"a2"}]'
console.log(JSON.stringify(arr2)); // '[{"id":1,"name":"a1"},{"id":1,"name":"a2"}]'

数组的splice方法

TypeScript 复制代码
let userList = ['Amy', 'Jack', "John", "Rose"]
//1是索引,添加或删除的指定位置 ,   0, 不删除, 'Nancy'表示添加的元素
// userList.splice(1, 0, 'Nancy')

//表示从索引为1的位置开始删除2个,之后再添加一个'Nancy'
// userList.splice(1, 2, 'Nancy')
userList.splice(1, 2)
console.log(userList.toString());

二维数组

TypeScript 复制代码
//二维数组
arr = [[1,2,],['a','b']]
arr[0][1]  // 2
相关推荐
KongHen5 小时前
UTS编写字符串编解码/加密插件(安卓及鸿蒙端)
前端·harmonyos
做运维的阿瑞5 小时前
鸿蒙6.0技术解析:五大行业迎来的智能化革命
人工智能·harmonyos
鸿蒙Jy5 小时前
一篇文章带你理解什么是鸿蒙开发中V1&&V2装饰器
harmonyos
SunkingYang5 小时前
C++变量与函数命名规范技术指南 (基于华为编码规范与现代C++最佳实践)
c++·华为·编码规范·命名规则·命名规范·函数名字·成员变量
王嘉俊9256 小时前
HarmonyOS 项目入门:构建跨设备智能应用的强大框架
华为·harmonyos
Francek Chen6 小时前
【HarmonyOS 6 特别发布】鸿蒙 6 正式登场:功能升级,构建跨设备安全流畅新生态
人工智能·华为·harmonyos·harmonyos 6
王嘉俊9256 小时前
HarmonyOS 分布式与 AI 集成:构建智能协同应用的进阶实践
人工智能·分布式·harmonyos
The 旺7 小时前
【案例实战】HarmonyOS分布式购物车:多设备无缝协同的电商体验
分布式·wpf·harmonyos
爱笑的眼睛117 小时前
HarmonyOS分布式Kit:解锁跨设备协同开发的无限可能
华为·harmonyos