HarmonyOS第六章:组件状态共享(父子组件传参、多层级组件传参、@Watch监听状态变化、@Observed与@ObjectLink、多层嵌套数据更新)

🎉 博客主页:【剑九_六千里-CSDN博客】【剑九_六千里-掘金社区
🎨 上一篇文章:【HarmonyOS第五章:组件抽取、构建函数抽取@Builder、构建函数插槽@BuilderParam
🎠 系列专栏:【HarmonyOS系列
💖 感谢大家点赞👍收藏⭐评论✍

在这里插入图片描述

在这里插入图片描述

引言:
ArkUI 框架,通过一系列装饰器(Decorators)提供了强大的数据绑定机制,使得开发者能够轻松地管理应用的状态并实现高效的视图更新。本文将深入探讨 ArkUI 中几种关键装饰器的功能及使用场景,包括 @Observed@Watch $$ 语法等。

1. 父子组件传值-单向数据流

1.1. 直接传递

  • 父组件使用子组件时,可直接按引用传递值

在这里插入图片描述
使用示例:

@Entry
@Component
struct Parent {
  @State count: number = 5;
  build() {
    Column() {
      Text(`这是父组件------${this.count}`)
      Child({count: this.count})
    }
      .height("100%")
      .width("100%")
  }
}

@Component
struct Child {
  count: number = 0;
  build() {
    Text(`这是子组件------${this.count}`)
  }
}
  • 父组件修改数据源时,传递给子组件的数据不更新

此时通过点击按钮,已经修改了数据源的数据,但子组件中并没有更新:

在这里插入图片描述

使用示例:

@Entry
@Component
struct Parent {
  @State count: number = 5;
  build() {
    Column() {
      Text(`这是父组件------${this.count}`)
      Button("修改count")
        .onClick(() => {
          this.count++;
        })
      Child({count: this.count})
    }
      .height("100%")
      .width("100%")
  }
}

@Component
struct Child {
  count: number = 0;
  build() {
    Text(`这是子组件------${this.count}`)
  }
}
  • 子组件不能修改数据

使用示例:
子组件修改数据,无反应,因为数据非响应式:

@Entry
@Component
struct Parent {
  @State count: number = 5;
  build() {
    Column() {
      Text(`这是父组件------${this.count}`)
      Button("修改count")
        .onClick(() => {
          this.count++;
        })
      Child({count: this.count})
    }
      .height("100%")
      .width("100%")
  }
}

@Component
struct Child {
  count: number = 0;
  build() {
    Column() {
      Text(`这是子组件------${this.count}`)
      Button("修改count")
        .onClick(() => {
          this.count++;
        })
    }
  }
}

1.2. @Prop变量装饰器

参数同步类型允许装饰的变量类型
单向同步string、number、boolean、enum类型,不支持any,不允许使用undefined和null,必须指定类型
  • 使用 @Prop 接收的参数可在子组件直接使用
  • 使用 @Prop 接收的参数,父组件修改数据,子组件会同步更新

在这里插入图片描述
使用示例:

@Entry
@Component
struct Parent {
  @State count: number = 5;
  build() {
    Column() {
      Text(`这是父组件------${this.count}`)
      Button("修改count")
        .onClick(() => {
          this.count++;
        })
      Child({count: this.count})
    }
      .height("100%")
      .width("100%")
  }
}

@Component
struct Child {
  @Prop count: number = 0;
  build() {
    Column() {
      Text(`这是子组件------${this.count}`)
    }
  }
}
  • @Prop装饰的变量可以在本地修改,但修改后的变化不会同步回其父组件,采用单向数据流的模式
    在这里插入图片描述

使用示例:

@Entry
@Component
struct Parent {
  @State count: number = 5;
  build() {
    Column() {
      Text(`这是父组件------${this.count}`)
      Button("修改count")
        .onClick(() => {
          this.count++;
        })
      Child({count: this.count})
    }
      .height("100%")
      .width("100%")
  }
}

@Component
struct Child {
  @Prop count: number = 0;
  build() {
    Column() {
      Text(`这是子组件------${this.count}`)
      Button("修改count")
        .onClick(() => {
          this.count++;
        })
    }
  }
}

2. 父子组件传值-双向数据流

2.1. @Link

参数同步类型允许装饰的变量类型
双向同步Object、class、string、number、boolean、enum类型,以及这些类型的数组,不支持any,不允许使用undefined和null,不支持Length、ResourceStr、ResourceColor类型
  • @Link 装饰的变量与其父组件中对应的数据源建立双向数据绑定,实现了双向同步。
  • 不能在 @Entry 装饰的自定义组件中使用。
  • @Link 修饰时数据不能设置默认值。
  • 父组件传递参数时,参数不能用 this.XXX,必须使用 $XXX
  • @Link 传递复杂数据类型时,只能将数据整体传递,不能只传递其中某一项。

2.1.1. 使用示例-基本的数据传递:

在这里插入图片描述

@Entry
@Component
struct Parent {
  @State count: number = 5;
  build() {
    Column() {
      Text(`这是父组件------${this.count}`)
      Child({count: $count}) // 必须使用$
    }
      .height("100%")
      .width("100%")
  }
}

@Component
struct Child {
  @Link count: number; // 不能设置默认值
  build() {
    Column() {
      Text(`这是子组件------${this.count}`)
    }
  }
}

2.1.2. 使用示例-父组件修改数据:

在这里插入图片描述

@Entry
@Component
struct Parent {
  @State count: number = 5;
  build() {
    Column() {
      Text(`这是父组件------${this.count}`)
      Button("修改count")
        .onClick(() => {
          this.count++;
        })
      Child({count: $count}) // 必须使用$
    }
      .height("100%")
      .width("100%")
  }
}

@Component
struct Child {
  @Link count: number; // 不能设置默认值
  build() {
    Column() {
      Text(`这是子组件------${this.count}`)
    }
  }
}

2.1.3. 使用示例-子组件修改数据源:

@Entry
@Component
struct Parent {
  @State count: number = 5;
  build() {
    Column() {
      Text(`这是父组件------${this.count}`)
      Button("修改count")
        .onClick(() => {
          this.count++;
        })
      Child({count: $count}) // 必须使用$
    }
      .height("100%")
      .width("100%")
  }
}

@Component
struct Child {
  @Link count: number; // 不能设置默认值
  build() {
    Column() {
      Text(`这是子组件------${this.count}`)
      Button("修改count")
        .onClick(() => {
          this.count++;
        })
    }
  }
}

2.1.4. 动态创建Link

3. 多层级组件之间传值

3.1. @Provide和@Consume

  • 父组件通过 @Provide 声明变量,@Provide 装饰的状态变量自动对其所有后代组件可用,即该变量被“provide”给他的后代组件。不需要多次在组件之间传递变量。
  • 后代通过使用 @Consume 去获取 @Provide 提供的变量,建立在 @Provide@Consume 之间的双向数据同步,与@State/@Link不同的是,前者可以在多层级的父子组件之间传递。
  • 父组件修改数据,后代组件同步更新。
  • 后代组件修改数据,父组件同步更新。
  • 接收数据时,不能设置默认值。
  • 允许传递 Objectclassstringnumberbooleanenum类型,以及这些类型的数组。

3.1.1. 使用示例-给孙组件传递数据:

在这里插入图片描述

@Entry
@Component
struct Parent {
  @Provide count: number = 5;
  build() {
    Column() {
      Text(`这是父组件------${this.count}`)
        .width("100%")
        .height(100)
        .backgroundColor(Color.Red)
      Child()
    }
    .height("100%")
    .width("100%")
  }
}

@Component
struct Child {
  build() {
    Column() {
      Text(`这是子组件`)
      GrandSon()
    }
    .width("100%")
    .height(200)
    .backgroundColor(Color.Green)
  }
}

@Component
struct GrandSon {
  @Consume count: number; // 不能设置默认值
  build() {
    Column() {
      Text(`这是孙组件------${this.count}`)
        .width("100%")
        .height(100)
        .backgroundColor(Color.Yellow)
    }
  }
}

3.1.2. 使用示例-父组件修改数据:

在这里插入图片描述

@Entry
@Component
struct Parent {
  @Provide count: number = 5;
  build() {
    Column() {
      Text(`这是父组件------${this.count}`)
        .width("100%")
        .height(100)
        .backgroundColor(Color.Red)
      Button("修改count")
        .onClick(() => {
          this.count++;
        })
      Child()
    }
    .height("100%")
    .width("100%")
  }
}

@Component
struct Child {
  build() {
    Column() {
      Text(`这是子组件`)
      GrandSon()
    }
    .width("100%")
    .height(200)
    .backgroundColor(Color.Green)
  }
}

@Component
struct GrandSon {
  @Consume count: number; // 不能设置默认值
  build() {
    Column() {
      Text(`这是孙组件------${this.count}`)
        .width("100%")
        .height(100)
        .backgroundColor(Color.Yellow)
    }
  }
}

3.1.3. 使用示例-后代组件修改数据:

在这里插入图片描述

@Entry
@Component
struct Parent {
  @Provide count: number = 5;
  build() {
    Column() {
      Text(`这是父组件------${this.count}`)
        .width("100%")
        .height(100)
        .backgroundColor(Color.Red)
      Button("修改count")
        .onClick(() => {
          this.count++;
        })
      Child()
    }
    .height("100%")
    .width("100%")
  }
}

@Component
struct Child {
  build() {
    Column() {
      Text(`这是子组件`)
      GrandSon()
    }
    .width("100%")
    .height(200)
    .backgroundColor(Color.Green)
  }
}

@Component
struct GrandSon {
  @Consume count: number; // 不能设置默认值
  build() {
    Column() {
      Text(`这是孙组件------${this.count}`)
        .width("100%")
        .height(100)
        .backgroundColor(Color.Yellow)
      Button("修改count")
        .onClick(() => {
          this.count++;
        })
    }
  }
}

4. @Observed 与 @ObjectLink 嵌套类对象属性变化

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

在这里插入图片描述

4.1. 下面这么传递的数据,在子组件中不能修改:

interface BookInfo {
  id: number;
  author: string;
  name: string;
  pageSize: number;
}

class BookItem implements BookInfo {
  id: number = 0;
  author: string = "";
  name: string = "";
  pageSize: number = 0;

  constructor(bookInfo: BookInfo) {
    this.id = bookInfo.id;
    this.author = bookInfo.author;
    this.name = bookInfo.name;
    this.pageSize = bookInfo.pageSize;
  }
}

@Entry
@Component
struct Parent {
  @State BookArr:BookInfo[] = [
    new BookItem({id: 1, author: "罗贯中", name: "西游记", pageSize: 20000}),
    new BookItem({id: 2, author: "施耐庵", name: "水浒传", pageSize: 30000}),
    new BookItem({id: 3, author: "曹雪芹", name: "红楼梦", pageSize: 20000}),
    new BookItem({id: 4, author: "吴承恩", name: "三国演义", pageSize: 30000}),
  ]
  build() {
    Column() {
      Text("这是父组件")

      ForEach(this.BookArr, (item: BookInfo) => {
        Child({item})
      })
    }
      .width("100%")
      .height("100%")
  }
}

@Component
struct Child {
  // 非响应式数据传递,需要默认值,否则会报错
  item:BookInfo = new BookItem({id: 1, author: "罗贯中", name: "西游记", pageSize: 20000});
  build() {
    Column() {
      Text(`${this.item.name}》--------${this.item.author}-------${this.item.pageSize}`)
    }
    .width("100%")
    .height(100)
    .backgroundColor(Color.Pink)
  }
}

4.2. 使用@Observed 和 @ObjectLink:

在这里插入图片描述

  • @ObjectLink 修饰的变量,不能设置默认值,否则会报错。
  • @ObjectLink 修饰的变量,必须用 @Observed 修饰的类作为类型。
interface BookInfo {
  id: number;
  author: string;
  name: string;
  pageSize: number;
}

@Observed
class BookItem implements BookInfo {
  public id: number = 0;
  public author: string = "";
  public name: string = "";
  public pageSize: number = 0;

  constructor(bookInfo: BookInfo) {
    this.id = bookInfo.id;
    this.author = bookInfo.author;
    this.name = bookInfo.name;
    this.pageSize = bookInfo.pageSize;
  }
}

@Entry
@Component
struct Parent {
  @State BookArr:BookInfo[] = [
    new BookItem({id: 1, author: "罗贯中", name: "西游记", pageSize: 20000}),
    new BookItem({id: 2, author: "施耐庵", name: "水浒传", pageSize: 30000}),
    new BookItem({id: 3, author: "曹雪芹", name: "红楼梦", pageSize: 20000}),
    new BookItem({id: 4, author: "吴承恩", name: "三国演义", pageSize: 30000}),
  ]
  build() {
    Column() {
      Text(`这是父组件`)
        .width("100%")
        .height(100)
        .backgroundColor(Color.Gray)

      List({space: 5}) {
        ForEach(this.BookArr, (item: BookInfo) => {
          ListItem() {
            Column() {
              Text(`这是初始数据:《${item.name}》--------${item.author}-------${item.pageSize}`)
              Child({item: item})
            }
          }
        })
      }
    }
      .width("100%")
      .height("100%")
  }
}

@Component
struct Child {
  // @ObjectLink 修饰的变量,不能设置默认值,否则会报错
  // 注意:此处得 item 必须用 @Observed 修饰的类作为类型
  @ObjectLink item:BookItem;
  build() {
    Column() {
      Text(`子组件《${this.item.name}》--------${this.item.author}-------${this.item.pageSize}`)
      Button("改变pageSize的值").onClick(() => {
        this.item.pageSize++;
      })
    }
    .width("100%")
    .height(100)
    .backgroundColor(Color.Pink)
  }
}

4.3. 可以发现,当我们在子组件中修改数据时,子组件试图更新了,那么父组件是否更新呢?再看下面这个例子:

在这里插入图片描述
我们在父组件修改数组第一项的数据,结果发现,子组件试图更新了,但是父组件试图却并没有更新:

interface BookInfo {
  id: number;
  author: string;
  name: string;
  pageSize: number;
}

@Observed
class BookItem implements BookInfo {
  public id: number = 0;
  public author: string = "";
  public name: string = "";
  public pageSize: number = 0;

  constructor(bookInfo: BookInfo) {
    this.id = bookInfo.id;
    this.author = bookInfo.author;
    this.name = bookInfo.name;
    this.pageSize = bookInfo.pageSize;
  }
}

@Entry
@Component
struct Parent {
  @State BookArr:BookInfo[] = [
    new BookItem({id: 1, author: "罗贯中", name: "西游记", pageSize: 20000}),
    new BookItem({id: 2, author: "施耐庵", name: "水浒传", pageSize: 30000}),
    new BookItem({id: 3, author: "曹雪芹", name: "红楼梦", pageSize: 20000}),
    new BookItem({id: 4, author: "吴承恩", name: "三国演义", pageSize: 30000}),
  ]
  build() {
    Column() {
      Text(`这是父组件`)
        .width("100%")
        .height(100)
        .backgroundColor(Color.Gray)
      Button("父组件修改原始数据").onClick(() => {
        this.BookArr[0].pageSize+=10
        console.log(`${this.BookArr[0].pageSize}`)
      })
      List({space: 5}) {
        ForEach(this.BookArr, (item: BookInfo) => {
          ListItem() {
            Column() {
              Text(`这是初始数据:《${item.name}》--------${item.author}-------${item.pageSize}`)

              Child({item: item})
            }
          }
        })
      }
    }
      .width("100%")
      .height("100%")
  }
}

@Component
struct Child {
  // @ObjectLink 修饰的变量,不能设置默认值,否则会报错
  // 注意:此处得 item 必须用 @Observed 修饰的类作为类型
  @ObjectLink item:BookItem;
  build() {
    Column() {
      Text(`子组件《${this.item.name}》--------${this.item.author}-------${this.item.pageSize}`)
      Button("改变pageSize的值").onClick(() => {
        this.item.pageSize++;
      })
    }
    .width("100%")
    .height(100)
    .backgroundColor(Color.Pink)
  }
}

将数组第一项数据输入到控制台,发现其实父组件的数组是更新了,只是试图未更新:

在这里插入图片描述

每个装饰器都有自己可以观察的能力,并不是所有的改变都可以被观察到,只有可以被观察到的变化才会进行UI更新。@Observed 装饰器可以观察到嵌套对象的属性变化,其他装饰器仅能观察到第一层的变化。

4.4. 解决 @Observed 与 @ObjectLink 试图更新问题:

多层嵌套的数据更新,子视图会更新,父组件视图不会更新。想让父组件视图更新,可以通过重新赋值的方式实现:
在这里插入图片描述

添加重新赋值的代码:

在这里插入图片描述

// 更新父组件视图
this.BookArr[0] = new BookItem(this.BookArr[0])

5. @Watch 装饰器:状态变量更改通知

@Watch 用于监听状态变量的变化,当状态变量变化时,@Watch 的回调方法将被调用。@WatchArkUI 框架内部判断数值有无更新使用的是严格相等(===),遵循严格相等规范。当在严格相等为 false 的情况下,就会触发 @Watch 的回调。

在这里插入图片描述

在这里插入图片描述

5.1. 使用示例-@Watch和@Prop结合使用

在这里插入图片描述

import promptAction from '@ohos.promptAction';

@Entry
@Component
struct Parent {
  @State @Watch("update") count: number = 5;

  update() {
    promptAction.showToast({
      message: `count值变成了${this.count}`
    })
  }
  build() {
    Column() {
      Text(`这是父组件------${this.count}`)
      Button("修改count")
        .onClick(() => {
          this.count++;
        })
      Child({count: this.count})
    }
    .height("100%")
    .width("100%")
  }
}

@Component
struct Child {
  @Prop count: number = 0;
  build() {
    Column() {
      Text(`这是子组件------${this.count}`)
      Button("修改count")
        .onClick(() => {
          this.count++;
        })
    }
  }
}

5.2. 使用示例-@Watch和@Link结合使用

在这里插入图片描述

import promptAction from '@ohos.promptAction';

@Entry
@Component
struct Parent {
  @State @Watch("update") count: number = 5;

  update() {
    promptAction.showToast({
      message: `count值变成了${this.count}`
    })
  }
  build() {
    Column() {
      Text(`这是父组件------${this.count}`)
      Button("修改count")
        .onClick(() => {
          this.count++;
        })
      Child({count: $count}) // 必须使用$
    }
    .height("100%")
    .width("100%")
  }
}

@Component
struct Child {
  @Link count: number; // 不能设置默认值
  build() {
    Column() {
      Text(`这是子组件------${this.count}`)
      Button("修改count")
        .onClick(() => {
          this.count++;
        })
    }
  }
}

5.3. 使用示例-@Watch和@Provide结合使用

在这里插入图片描述
使用时,@Watch 放在紧跟 @Provide 之后的位置:

import promptAction from '@ohos.promptAction';

@Entry
@Component
struct Parent {
  @Provide @Watch("update") count: number = 5;

  update() {
    promptAction.showToast({
      message: `count值变成了${this.count}`
    })
  }
  build() {
    Column() {
      Text(`这是父组件------${this.count}`)
        .width("100%")
        .height(100)
        .backgroundColor(Color.Red)
      Button("修改count")
        .onClick(() => {
          this.count++;
        })
      Child()
    }
    .height("100%")
    .width("100%")
  }
}

@Component
struct Child {
  build() {
    Column() {
      Text(`这是子组件`)
      GrandSon()
    }
    .width("100%")
    .height(200)
    .backgroundColor(Color.Green)
  }
}

@Component
struct GrandSon {
  @Consume count: number; // 不能设置默认值
  build() {
    Column() {
      Text(`这是孙组件------${this.count}`)
        .width("100%")
        .height(100)
        .backgroundColor(Color.Yellow)
      Button("修改count")
        .onClick(() => {
          this.count++;
        })
    }
  }
}

7. $$语法:内置组件双向同步

$$运算符为系统内置组件提供TS变量的引用,使得TS变量和系统内置组件的内部状态保持同步。

7.1. 使用规则

  • 当前 $$ 支持基础类型变量,以及@State@Link@Prop装饰的变量。
  • 当前 $$ 支持的组件:
    在这里插入图片描述在这里插入图片描述
  • $$ 绑定的变量变化时,会触发UI的同步刷新。

7.2. 使用示例

TextInput 方法的 text 参数为例:
在这里插入图片描述

@Entry
@Component
struct TextInputExample {
  @State text: string = ''
  controller: TextInputController = new TextInputController()

  build() {
    Column({ space: 20 }) {
      Text(this.text)
      TextInput({ text: $$this.text, placeholder: '请输入', controller: this.controller })
        .placeholderColor(Color.Grey)
        .placeholderFont({ size: 14, weight: 400 })
        .caretColor(Color.Blue)
        .width(300)
    }
      .width('100%')
      .height('100%')
      .justifyContent(FlexAlign.Center)
  }
}
  • 12
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

剑九_六千里

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值