对象数组不能深层监听
效果图:
![[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 完整代码
注意事项:
-
被观察的对象必须是Class类型(参考代码第11行)
-
被观察对象要添加@Observe装饰器 (参考代码第10行)
-
对象数组的每一项由class实例组成 (参考代码第33行)
-
必须由父组件将Class实例数据传入自定义子组件使用 (参考代码第63行)
-
子组件中用@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