积分582 / 贡献0

提问0答案被采纳0文章52

[经验分享] OpenHarmony应用组件间通信 原创 精华

Laval社区小助手 显示全部楼层 发表于 2024-6-4 10:37:58

前言

ETS(基于TS扩展的声明式开发范式)提供了强大的组件化能力,支持开发者高效的构建应用的UI界面,然而每一个组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互进行直接的引用,因此组件间的相互通信是非常重要的。

组件间通信原理

ETS组件间通信是通过组合不同的装饰器来实现数据在各个组件之间的单向/双向传递。

相关装饰器介绍

装饰器名称 功能 特征
@State 所装饰变量的值发生变化时,将会调用所在组件的build方法进行UI刷新 支持装饰多种数据类型的变量(不允许object和any);允许组件内存在多个被其装饰的变量;内部私有(@State装饰的的变量是私有变量,只能在组件内访问);需要本地初始化(必须为所有@State装饰的变量分配初始值,否则可能导致框架行为未定义);创建自定义组件时支持通过状态变量名设置初始值
@Prop 具备@State的功能,装饰的变量能够和父组件中被@State装饰的变量建立单向数据绑定(只支持父组件传递数据至子组件),用于父组件向子组件传递数据 仅支持装饰简单类型(boolean,number,string)的变量;仅允许在组件内访问;允许组件内存在多个被其装饰的变量;装饰的所有变量必须使用其父组件提供的被@State装饰的变量进行初始化,不支持在组件内部进行初始化
@Link 具备@State的功能,装饰的变量可以和父组件中被@State装饰的变量建立双向数据绑定,用于父子组件间数据的双向传递 支持装饰多种数据类型的变量;仅在组件内访问; 双向通信(子组件对@Link装饰的变量的更改将同步修改父组件中被@State装饰的变量), 装饰的所有变量必须使用其父组件提供的被@State或@Link装饰的变量进行初始化,不支持在组件内部进行初始化;进行数据传递时需要通过'\$'操作符将父组件的数据传给子组件
@Provide 具备@State的功能,可以和子/孙组件中被@Consume装饰的变量建立双向数据绑定 支持装饰多种数据类型的变量;该装饰器可以指定参数,用于给装饰的变量起别名,推荐使用@Provide("alias")这种形式;需要指定初始值;一般和@Consume搭配使用;传递数据的方式比@Link/@Prop更简单,只要@Consume的参数值和@Provide的参数值一致就可以进行数据的双向传递,若两者都没有参数值,则它们装饰的变量名必须相同;@Provide/@Consume可以代替@State/@Link传递数据,但是更适用于组件数据的跨层级传递的场景(例如组件1嵌套了组件2,组件2嵌套组件3,组件3嵌套了组件4,要实现组件1和组件4之间的数据传递,使用@Provide/@Consume进行数据传递会更加方便)
@Consume 具备@State的功能,与@Provide搭配使用 不可设置默认值,必须和@Provide一起使用;使用@Provide 和@Consume时避免循环引用导致死循环;使用了@Consume装饰器的组件上不能有@Entry装饰,否则程序会报错
@StorageLink 具备@State的功能,能与AppStorage建立双向数据绑定(AppStorage是应用程序中的单例对象,与应用的生命周期相同,为应用程序范围内的可变状态属性提供中央存储) 支持装饰多种数据类型的变量; 被其装饰的变量即可以在组件内部初始化,也可以使用AppStorage中的值初始化;与AppStorage建立双向数据绑定,在UI组件中对@StorageLink装饰的变量所做的更改将同步到AppStorage,并从AppStorage同步到任何其他绑定实例中;应用范围内的任意两个或多个组件都可以借助AppStorage进行数据传递
@Watch 用于监听被指定装饰器所装饰变量的变化 需要指定参数值,参数值是方法的名称;装饰的变量上必须有其他装饰器装饰;可以监听装饰器@State、@Prop、@Link、@ObjectLink、@Provide、@Consume、@StorageLink装饰的变量的变化(注意:监听@StorageLink装饰的变量时,@StorageLink的参数值必须和其装饰的变量名一致)

组件间通信实现

父子组件通信

数据单向传递

使用@State和@Prop装饰器可以实现父组件向子组件单向传递数据。

  • 定义子组件Child
@Component
struct Child {
    @Watch("listenChildCount") //当count变量值发生变化,会调用listenChildCount()
    @Prop
    count: number;
    build() {
        Column() {
                Text("子组件:" + this.count).fontSize(30)
                Button("reduce")
                    .fontSize(25)
                    .onClick(() => {
                        this.count--;
                    })
            }
            .border({ width: 2, color: Color.Blue })
            .backgroundColor(Color.Green)
            .width("50%")
            .height("50%")
    }

    /**
     * 用于监听count变量的变化
     */
    listenChildCount() {
        console.info("ChildComponent count changed:" + this.count)
    }
}
  • 定义父组件,在父组件中嵌套子组件Child,并将父组件中的count变量传递给子组件
@Entry
@Component
struct Parent {
    @State
    @Watch("listenCount") //当count变量值发生变化,会调用listenCount()
    count: number = 0;
    build() {
        Column() {
            Column() {
                    Text("父组件:" + this.count)
                        .fontSize(30)
                        .fontColor(Color.Red)
                    Button("add")
                        .fontColor(Color.White)
                        .fontSize(30)
                        .backgroundColor(Color.Orange)
                        .onClick(() => {
                            this.count++;
                        })
                    Child({ count: this.count })
                }.border({ width: 2, color: Color.Black })
                .width("100%")
                .height(400)
                .backgroundColor(Color.Gray)
        }
    }
    listenCount() {
        console.info("ParentComponent count changed:" + this.count)
    }
}

效果如下:

数据双向传递

@State/@Link实现父子组件数据双向传递

  • 将上文的子组件中count变量上的装饰器改为@Link
@Component
struct Child {
    @Link
    @Watch("listenCount")
    count: number;
    build() {
            ...
            ...
        }
        ...
}
  • 改变上文中父组件传递数据到子组件的方式
@Entry
@Component
struct Parent {
    ...
    build() {
        Column() {
            ...
            ...
            Child({ count: $count }) //@Link需要用'$'符号传递变量
        }.border({ width: 2, color: Color.Black })
        .width("100%")
        .height(400)
        .backgroundColor(Color.Gray)

    }
    ...
}

效果如下:

@Provide/@Consume实现父子组件数据双向传递

将上文父组件中count变量上的装饰器替换为@Provide,子组件中count变量上的装饰器替换成@Consume:

@Entry
@Component
struct Parent {
    //这里@Provide必须放在@Watch的上面,否则会报错
    @Provide("Count")
    @Watch("listenCount") //当count变量值发生变化,会调用listenCount()
    count: number = 0;
    build() {
        Column() {
            Column() {
                    Text("父组件:" + this.count)
                        .fontSize(30)
                        .fontColor(Color.Red)

                    Button("add")
                        .fontColor(Color.White)
                        .fontSize(30)
                        .backgroundColor(Color.Orange)
                        .onClick(() => {
                            this.count++;
                        })
                    Child() //使用@Provide 、@Consume不需要像@Link、@Prop那样传数据
                }.border({ width: 2, color: Color.Black })
                .width("100%")
                .height(400)
                .backgroundColor(Color.Gray)
        }
    }
    listenCount() {
        console.info("ParentComponent count changed:" + this.count)
    }
}

@Component
struct Child {
    //这里@Consume必须放在@Watch的上面,否则会报错
    @Consume("Count") //@Consume的参数值必须和对应的@Provide的参数值一致
    @Watch("listenChildCount") //当count变量值发生变化,会调用listenChildCount()
    count: number;
    build() {
        Column() {
            Text("子组件:" + this.count)
                .fontSize(30)
            Button("reduce")
                .fontSize(25)
                .onClick(() => {
                    this.count--;
                })
        }
        .border({ width: 2, color: Color.Blue })
        .backgroundColor(Color.Green)
        .width("50%")
        .height("50%")
    }
    listenChildCount() {
        console.info("ChildComponent count changed:" + this.count)
    }
}

效果:效果与上一步相同

@Provide/@Consume实现组件跨层级双向传递数据

  • 定义子组件GrandChild
@Component
struct GrandChild {
    @Consume
    count: number;
    build() {
        Column() {
            Text("GrandChild:" + this.count)
                .fontSize(30)
            Button("reduce")
                .fontSize(25)
                .onClick(() => {
                    this.count--;
                })
        }
        .border({ width: 2, color: Color.Blue })
        .backgroundColor(Color.Gray)
        .width(200)
        .height(180)
    }
}
  • 定义子组件Child,在Child组件中嵌套GrandChild组件
@Component
struct Child {
    build() {
        Column() {
            Text("Child")
                .fontSize(40)
                .margin({ top: 25 })
            GrandChild()
        }
        .border({ width: 2, color: Color.Blue })
        .backgroundColor(Color.Green)
        .width(300)
        .height(300)
    }
}
  • 定义父组件Parent,在Parent组件中嵌套Child组件
@Entry
@Component
struct Parent {
    @Provide("Count")
    count: number = 0;
    build() {
        Column() {
            Column() {
                Text("Parent:" + this.count)
                    .fontSize(30)
                    .fontColor(Color.Red)

                Button("add")
                    .fontColor(Color.White)
                    .fontSize(30)
                    .backgroundColor(Color.Orange)
                    .onClick(() => {
                        this.count++;
                    })
                Child()
            }.border({ width: 2, color: Color.Black })
            .width("100%")
            .height(400)
            .backgroundColor(Color.Gray)
        }
    }
}

效果如下:

兄弟组件通信

ETS中要实现兄弟组件间传递数据需要借助第三个组件,即两个需要传递数据的组件需要有共同的父组件

@Provide/@Consume实现兄弟组件双向传递数据

  • 定义子组件Child1、Child2,并用@Consume装饰对应变量
@Component
struct Child1 {
    @Consume("Count")
    count1: number
    build() {
        Column() {
            Text("Child1:" + this.count1)
                .fontSize(40)
            Button("add")
                .fontColor(Color.White)
                .fontSize(30)
                .backgroundColor(Color.Orange)
                .onClick(() => {
                    this.count1++;
                })
        }
        .border({ width: 2, color: Color.Blue })
        .backgroundColor(Color.Green)
        .width("100%")
        .height(180)
    }
}

@Component
struct Child2 {
    @Consume("Count")
    count2: number;
    build() {
        Column() {
            Text("Child2:" + this.count2)
                .fontSize(30)
            Button("reduce")
                .fontSize(25)
                .onClick(() => {
                    this.count2--;
                })
        }
        .border({ width: 2, color: Color.Blue })
        .backgroundColor(Color.Yellow)
        .width("100%")
        .height(180)
    }
}
  • 定义父组件Parent,嵌套组件Child1、Child2
@Entry
@Component
struct Parent {
    @Provide("Count")
    count: number = 0;
    build() {
        Column() {
            Child1()
            Child2()
        }
        .width("100%")
        .height("100%")
    }
}

效果如下:

@State和@Link实现兄弟组件双向传递数据

  • 将子组件Child1和Child2中count变量上的装饰器改为@Link
@Component
struct Child1 {
    @Link
    count1: number
    build() {
        ...
        ..
    }

}
@Component
struct Child2 {
    @Link
    count2: number;
    build() {
        ...
        ...
    }
}
  • 将上一步父组件Parent中count变量上的装饰器改为@State
@Entry
@Component
struct Parent {
    @State
    count: number = 0;
    build() {
        ...
        Child1({ count1: $count })
        Child2({ count2: $count })
            ...
    }
}

效果:与上一步的效果相同

任意组件通信

借助AppStorage和@StorageLink可以实现任意两个组件(包括同一个页面里的两个组件、不同页面里的组件、不同Ability里的两个组件)

AppStorage/@StorageLink实现不同页面组件间传递数据

  • 定义页面Page1
import router from '@ohos.router'; //router用于进行页面跳转
AppStorage.SetOrCreate("Count", 0); //先向AppStorage中存一个key:value
@Entry
@Component
struct Component1 {
    @StorageLink("Count")
    count: number = AppStorage.Link("Count") //与AppStorage进行双向绑定,用AppStorage里的值初始化count
    build() {
        Column() {
            Text("页面1:" + this.count)
                .fontSize(50)

            Button("add")
                .margin({ bottom: 25 })
                .onClick(() => {
                    this.count++;
                })
            Button("跳转")
                .fontSize(25)
                .onClick(() => {
                    router.push({ url: "pages/page2" })
                })
        }.border({ width: 2, color: Color.Blue })
        .width("100%")
        .height(400)
        .backgroundColor(Color.Gray)
    }
}
  • 定义页面Page2
import router from '@ohos.router';
@Entry
@Component
struct Component2 {
    @StorageLink("Count")
    count: number = AppStorage.Link("Count") //与AppStorage进行双向绑定,用AppStorage里的值初始化count
    build() {
        Column() {
            Text("页面2:" + this.count)
                .fontSize(50)
                .fontColor(Color.Green)

            Button("reduce")
                .margin({ bottom: 25 })
                .onClick(() => {
                    this.count--;
                })
            Button("跳转")
                .fontSize(25)
                .onClick(() => {
                    router.back({ url: "pages/page1" })
                })
        }.border({ width: 2, color: Color.Blue })
        .width("100%")
        .height(400)
        .backgroundColor(Color.Orange)
    }
}

效果如下:

参考文献

[1] 组件参考 (基于TS扩展的声明式开发范式). https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/Readme-CN.md

[2] UI开发. https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/ui/Readme-CN.md

©著作权归作者所有,转载或内容合作请联系作者

您尚未登录,无法参与评论,登录后可以:
参与开源共建问题交流
认同或收藏高质量问答
获取积分成为开源共建先驱

Copyright   ©2023  OpenHarmony开发者论坛  京ICP备2020036654号-3 |技术支持 Discuz!

返回顶部