【HarmonyOS】HarmonyOS NEXT学习日记:八、组件通信
通过前面的学习我们基本上掌握了如何封装组件,但是实际使用过程中组件之间的状态需要互相之间关联通讯,涉及到父子组件,后代组件之间的相互通信。
@State装饰器:组件内状态
这是最常用的一种装饰器,它装饰的变量不与父组件中任何类型的变量同步。
@State装饰的变量,与声明式范式中的其他被装饰变量一样,是私有的,只能从组件内部访问,在声明时必须指定其类型和本地初始化。初始化也可选择使用命名参数机制从父组件完成初始化。
@State装饰的变量拥有以下特点:
@State装饰的变量与子组件中的@Prop装饰变量之间建立单向数据同步,与@Link、@ObjectLink装饰变量之间建立双向数据同步。
@State装饰的变量生命周期与其所属自定义组件的生命周期相同。
@Prop装饰器:父子单向同步
@Prop装饰的变量可以和父组件建立单向的同步关系。@Prop装饰的变量是可变的,但是变化不会同步回其父组件。
代码示例:
我们在父组件定义一个title,将其传给子组件,子组件使用@Prop装饰的状态来接收
父组件:
// Index.ets
import text from '@ohos.graphics.text'
import { router } from '@kit.ArkUI';
import { MyListItem } from '../components/MyListItem'
@Entry
@Component
struct Index {
@State title:string = 'parent'
build() {
Column(){
Text('parentTitle:'+this.title)
.fontSize(36)
.fontColor(Color.Red)
Text('childreTitle:').fontSize(36)
MyListItem({title:this.title,onToDetail:()=>{
router.pushUrl({url: 'pages/Second'})
}})
}
}
}
子组件:
//MyListItem
@Component
export struct MyListItem {
@Prop title: string
onToDetail: Function = () => {
}
aboutToAppear(){
this.title='children'
}
build() {
Text(this.title).fontSize(50).onClick(() => {
this.onToDetail()
})
}
}
可以看到,我们在子组件的aboutToAppear中将title修改为了’children‘,在子组件区域也确实展示为了children,但是父组件依然没有改变,是’parent‘。
那么我们修改父组件的title,子组件是什么表现呢?
修改父组件代码为:
// Index.ets
import text from '@ohos.graphics.text'
import { router } from '@kit.ArkUI';
import { MyListItem } from '../components/MyListItem'
@Entry
@Component
struct Index {
@State title:string = 'parent'
build() {
Column(){
Text('parentTitle:'+this.title)
.fontSize(36)
.fontColor(Color.Red)
.onClick(()=>{
this.title='parentChanged'
})
Text('childreTitle:').fontSize(36)
MyListItem({title:this.title,onToDetail:()=>{
router.pushUrl({url: 'pages/Second'})
}})
}
}
}
页面初始化为
当我们点击文本触发新添加的点击事件修改父组件的title时:
子组件的@Prop title一起改变。
@Link装饰器:父子双向同步
子组件中被@Link装饰的变量与其父组件中对应的数据源建立双向数据绑定。
我们还是写一个代码来看看效果
父组件:
// Index.ets
import text from '@ohos.graphics.text'
import { router } from '@kit.ArkUI';
import { MyListItem } from '../components/MyListItem'
@Entry
@Component
struct Index {
@State title:string = 'parent'
build() {
Column(){
Text('parentTitle:'+this.title)
.fontSize(36)
.fontColor(Color.Red)
.onClick(()=>{
this.title='parentChanged'
})
Text('childrenTitle:').fontSize(36)
MyListItem({title:this.title})
}
}
}
子组件:
//MyListItem
@Component
export struct MyListItem {
@Link title:string
aboutToAppear(){
console.log('title',this.title)
}
build() {
Text(this.title).fontSize(50)
.onClick(()=>{this.title='childrenChanged'})
}
}
初始化页面
点击红色文字,修改父组件状态
点击最下方的黑色文字,修改子组件状态
至此,就实现了父子组件的状态同步通信。
问题
我们试着传递一个两层的对象看看
父组件:
// Index.ets
import text from '@ohos.graphics.text'
import { router } from '@kit.ArkUI';
import { MyListItem } from '../components/MyListItem'
import {childrenObj,title} from '../components/MyListItem'
@Entry
@Component
struct Index {
@State myTitle:title = {
txt: 'parent'
}
@State obj:childrenObj = {
title: this.myTitle
}
build() {
Column(){
Text('parentTitle:'+this.obj.title.txt)
.fontSize(36)
.fontColor(Color.Red)
.onClick(()=>{
console.log(this.obj.title.txt)
this.obj.title.txt='parentChanged'
})
Text('childrenTitle:').fontSize(36)
MyListItem({obj:this.obj})
}
}
}
子组件:
//MyListItem
interface childrenObj{
title: title
}
interface title{
txt: string
}
@Component
export struct MyListItem {
@Link obj:childrenObj
aboutToAppear(){
console.log('title',this.obj.title.txt)
}
build() {
Text(this.obj.title.txt).fontSize(50)
.onClick(()=>{this.obj.title.txt='childrenChanged'})
}
}
export {childrenObj,title}
!!!!!!!!!!!!无论如何修改obj.title.txt的值,页面都没有响应,这个状态对于页面来说,失去响应式了。
事实上在实际应用开发中,应用会根据开发需要,封装自己的数据模型。对于多层嵌套的情况,比如二维数组,或者数组项class,或者class的属性是class,他们的第二层的属性变化是无法观察到的。
如何解决我们稍后再说。!!!!!!!!!!!!!!
@Provide装饰器和@Consume装饰器:与后代组件双向同步
有的时候我们不仅仅需要父子之间传值,还需要后代组件和祖先组件之后跨层级传值,这种情况就需要用到 @Provide装饰器和@Consume装饰器。
@Provide和@Consume,应用于与后代组件的双向数据同步,应用于状态数据在多个层级之间传递的场景。不同于上文提到的父子组件之间通过命名参数机制传递,@Provide和@Consume摆脱参数传递机制的束缚,实现跨层级传递。
其中@Provide装饰的变量是在祖先组件中,可以理解为被“提供”给后代的状态变量。@Consume装饰的变量是在后代组件中,去“消费(绑定)”祖先组件提供的变量。
我们在刚刚代码的基础上加一层组件Intermediate,让MyListItem组件变成Index下两层后代组件
Index:
// Index.ets
import text from '@ohos.graphics.text'
import { router } from '@kit.ArkUI';
import { Intermediate } from '../components/Intermediate'
@Entry
@Component
struct Index {
@Provide title:string = 'parent'
build() {
Column(){
Text('parentTitle:'+this.title)
.fontSize(36)
.fontColor(Color.Red)
.onClick(()=>{
this.title='parentChanged'
})
Text('childrenTitle:').fontSize(36)
Intermediate()
}
}
}
Intermediate:
//Intermediate
import { MyListItem } from './MyListItem'
@Component
export struct Intermediate {
build() {
MyListItem()
}
}
MyListItem:
//MyListItem
@Component
export struct MyListItem {
@Consume title:string
build() {
Text(this.title).fontSize(50)
.onClick(()=>{this.title='childrenChanged'})
}
}
点击红色文字修改index状态
点击黑色文字修改后代组件状态
即完成了祖先组件和后代组件的双向状态绑定。
@Observed装饰器和@ObjectLink装饰器:嵌套类对象属性变化
好的现在我们来解决一下刚刚发现的问题,当对象嵌套两层之后,状态失去了响应性。
// Index.ets
//MyListItem
@Observed
class ClassA {
public c: string;
constructor(c: string) {
this.c = c;
}
}
@Observed
class ClassB {
public a: ClassA;
public b: string;
constructor(a: ClassA, b: string) {
this.a = a;
this.b = b;
}
}
@Component
struct MyListItem {
@ObjectLink a:ClassA
build() {
Text(this.a.c).fontSize(50)
.onClick(()=>{
this.a.c = 'ca';
})
}
}
@Entry
@Component
struct Index {
@State b: ClassB = new ClassB(new ClassA('a'),'b');
build() {
Column(){
MyListItem({a:this.b.a})
}
.width('100%')
.onClick(()=>{
this.b.a.c='pa'
})
}
}
点击父元素,给b.a.c赋值’pa’
点击子组件给a.c 赋值 ‘ca’
$$内置组件双向同步
很多原生的组件我们要双向同步状态可以使用 , , ,运算符为系统内置组件提供TS变量的引用,使得TS变量和系统内置组件的内部状态保持同步。
@Entry
@Component
struct TextInputExample {
@State text: string = ''
controller: TextInputController = new TextInputController()
build() {
Column({ space: 20 }) {
Text(this.text)
TextInput({ text: $$this.text, placeholder: 'input your word...', controller: this.controller })
.placeholderColor(Color.Grey)
.placeholderFont({ size: 14, weight: 400 })
.caretColor(Color.Blue)
.width(300)
}.width('100%').height('100%').justifyContent(FlexAlign.Center)
}
}
可以观察到输入框绑定值改变时,ui上的相同变量也一起刷新了。
@Watch装饰器:状态变量更改通知
@Watch应用于对状态变量的监听。如果开发者需要关注某个状态变量的值是否改变,可以使用@Watch为状态变量设置回调函数。
@Component
struct TotalView {
@Prop @Watch('onCountUpdated') count: number = 0;
@State total: number = 0;
// @Watch 回调
onCountUpdated(propName: string): void {
this.total += this.count;
}
build() {
Text(`Total: ${this.total}`)
}
}
@Entry
@Component
struct CountModifier {
@State count: number = 0;
build() {
Column() {
Button('add to basket')
.onClick(() => {
this.count++
})
TotalView({ count: this.count })
}
}
}
当子组件接收的count变化时会触发watch的回调onCountUpdated,在其中修改count状态的的值