用户云卷云舒 发表于 2024-12-7 09:50:34

HarmonyOS Next 组件或页面之间的所有通信(传参)方法总结

系列文章目次

【鸿蒙】HarmonyOS NEXT开发快速入门教程之ArkTS语法装饰器(上)
【鸿蒙】HarmonyOS NEXT开发快速入门教程之ArkTS语法装饰器(下)
【鸿蒙】HarmonyOS NEXT应用开发快速入门教程之布局篇(上)
【鸿蒙】HarmonyOS NEXT应用开发快速入门教程之布局篇(下)
HarmonyOS Next 系列之省市区弹窗选择器实现(一)
HarmonyOS Next 系列之验证码输入组件实现(二)
HarmonyOS Next 系列之底部标签栏TabBar实现(三)
HarmonyOS Next 系列之HTTP哀求封装和Token持久化存储(四)
HarmonyOS Next 系列之从手机选择图片或拍照上传功能实现(五)
HarmonyOS Next 系列之可移动悬浮按钮实现(六)
HarmonyOS Next 系列之沉醉式状态实现的多种方式(七)
HarmonyOS Next系列之Echarts图表组件(折线图、柱状图、饼图等)实现(八)
HarmonyOS Next系列之地图组件(Map Kit)使用(九)
HarmonyOS Next系列之半圆环进度条实现(十)
HarmonyOS Next 系列之列表下拉革新和触底加载更多数据实现(十一)
HarmonyOS Next系列之实现一个左右暴露中间大两边小带缩放动画的轮播图(十二)
HarmonyOS Next系列之水波纹动画殊效实现(十三)


前言

本文总结了鸿蒙中常用的组件或页面之间参数通报方法,包括父子组件,子孙组件,兄弟组件,互不相关的组件(页面)和页面路由跳转的参数通报以及父子组件方法互调等。
一、父子组件参数通报

1、父传子

方法一:@Prop

阐明:子组件定义@Prop变量,父组件通过对象形式传入数据
示例:
@Entry
/**
* 父组件
*/
@Component
struct Parent {
@State title:string='鸿蒙系统'

build() {
    Column() {
      Child({title:this.title})
    }
    .width('100%')
    .height('100%')
}
}

/**
* 子组件
*/
@Component
struct Child {
@Prop title:string;
build() {
    Text(`我是子组件标题:${this.title}`)
}
}
运行结果:
https://i-blog.csdnimg.cn/direct/1dfac9336d0a4ef5bf4fe5a9877a06a3.png
方法二:@Link

阐明:子组件定义@Link变量,父组件通过对象形式传入数据
@Entry
/**
* 父组件
*/
@Component
struct Parent {
@State title:string='鸿蒙系统'

build() {
    Column() {
      Child({title:this.title})
    }
    .width('100%')
    .height('100%')
}
}

/**
* 子组件
*/
@Component
struct Child {
@Link title:string;
build() {
    Button(this.title)
}
}
运行结果:
https://i-blog.csdnimg.cn/direct/7aea934032b043daa3910597e15e3450.png
方法三:普通变量

阐明:子组件定义普通变量,父组件通过对象形式传入数据
@Entry
/**
* 父组件
*/
@Component
struct Parent {
@State title:string='普通变量'

build() {
    Column() {
      Child({title:this.title})
    }
    .width('100%')
    .height('100%')
}
}

/**
* 子组件
*/
@Component
struct Child {
private title:string='';
build() {
    Text(`我是${this.title}`)
}
}
运行结果:
https://i-blog.csdnimg.cn/direct/6e1af6b636234fb5b43ce2c76c04d11d.png
三种方法区别

1、@Prop数据为单向通报,只能从父组件通报到子组件,无法从子组件回传到父组件。也就是子组件被@Prop修饰的变量值改变后,对应的父组件数据稳定。
2、@Link数据为双向绑定,子组件被@Link修饰的变量值改变,对应的父组件的数据也跟着变化,而且具有UI响应变化
3、普通变量只在子组件初始化时候能拿到父组件通报的数据,后续假如父组件改变值子组件将无法获取到最新值
示例1:

通过改变父组件值看看对应子组件数据变化环境
@Entry
/**
* 父组件
*/
@Component
struct Parent {
@State prop:string='prop方式'
@State link:string='link方式'
@State common:string="普通方式"

build() {
    Column({space:10}) {
      Child({prop:this.prop})
      Child2({link:this.link})
      Child3({common:this.common})

      Button('父组件改变值').onClick(()=>{
      this.prop='父组件改变后的prop'
      this.link='父组件改变后的link'
      this.common='父组件改变后的普通方式'
      })
    }
    .width('100%')
    .height('100%')
}
}

/**
* 子组件
*/
@Component
struct Child {
@Prop prop:string;
build() {
    Row(){
      Text(`我是子组件prop变量,值为:${this.prop}`).fontColor(Color.Red)
    }
}
}
/**
* 子组件
*/
@Component
struct Child2 {
@Link link:string;
build() {
    Row(){
      Text(`我是子组件link变量,值为:${this.link}`).fontColor(Color.Red)
    }
}
}
/**
* 子组件
*/
@Component
struct Child3 {
private common?:string='';
build() {
    Row(){
      Text(`我是子组件普通变量,值为:${this.common}`).fontColor(Color.Red)
    }
}
}
运行结果:
https://i-blog.csdnimg.cn/direct/60e5c4705c9c4ccea197dd0fe328acfa.gif
阐明:从运行结果可以看出普通变量方式只能在首次拿到数据后续无法响应UI变化,而@Prop和@Link可以。
示例2:

通过改变子组件值看看父组件数据变化环境
@Entry
/**
* 父组件
*/
@Component
struct Parent {
@State prop:string='prop方式'
@State link:string='link方式'
@State common:string="普通方式"

build() {
    Column({space:10}) {
      Text(`父组件prop值为:${this.prop}`)
      Text(`父组件link值为:${this.link}`)
      Text(`父组件common值为:${this.common}`)

      Child({prop:this.prop})
      Child2({link:this.link})
      Child3({common:this.common})

    }
    .width('100%')
    .height('100%')
}
}

/**
* 子组件
*/
@Component
struct Child {
@Prop prop:string;
build() {
    Row(){
      Button('改变子组件prop值').onClick(()=>{
      this.prop="新prop"
      })
    }
}
}
/**
* 子组件
*/
@Component
struct Child2 {
@Link link:string;
build() {
    Row(){
      Button('改变子组件link值').onClick(()=>{
      this.link="新link"
      })
    }
}
}
/**
* 子组件
*/
@Component
struct Child3 {
privatecommon?:string='';

build() {
    Row(){
      Button('改变子组件common值').onClick(()=>{
      this.common="新common"
      })
    }
}
}
运行结果:
https://i-blog.csdnimg.cn/direct/7852198bd0da451d9b5bf9197cd4849b.gif
阐明:从运行结果可以看出@Link绑定的变量改变同时父组件数据跟着变,另两个父组件数据不受影响。
方法四:@Provide+@Consume

见下文先容
方法五:@LocalStorageProp或@LocalStorageLink

见下文先容
方法六:@StorageProp和@StorageLink

见下文先容
方法七:eventHub

见下文先容
方法八:globalThis

见下文先容
2、子传父

子传父需求场景一样平常都是一些变乱参数通报,如点击变乱回调等
方法:父组件通报一个函数参数给子组件,子组件调用函数并传入入参,父组件就可以拿到该入参
示例:
@Entry
/**
   * 父组件
   */
@Component
struct Parent {
@State name: string = ''

build() {
    Column({ space: 10 }) {
      Text(`姓名是:${this.name}`)
      Child({
      clickAction: (name) => {
          this.name = name
      }
      })

    }
    .width('100%')
    .height('100%')
}
}

/**
* 子组件
*/
@Component
struct Child {
//回调函数
private clickAction: (name: string) => void = () => {}

build() {
    Row() {
      Button('获取姓名').onClick(() => {
      this.clickAction('小明')
      })
    }
}
}

运行结果:
https://i-blog.csdnimg.cn/direct/1b07da525e674640a089325cb6e66aff.gif
二、爷孙组件参数通报

方法一:@Provide和@Consume

   阐明@Provide和@Consume组合可以把参数往子孙层通报,不单单支持爷孙组件之间,支持跨层级(多层级、不限层级)通报。
而且数据是双向绑定的和@Link一样,改变孙子组件数据,爷爷组件数据也会跟着变化
ps:以是@Provide和@Consume也支持父子组件参数通报,但其更多用在跨层级组件之间数据通报,父子组件优先使用上面先容前三种。
示例:
//父组件
@Entry
@Component
struct Parent {
@Provide('weight') weight: number = 50

build() {
    Column({ space: 20 }) {
      Text(`爷组件体重值:${this.weight}`)
      Button(`爷组件件体重+1`).onClick(() => {
      this.weight++
      })
      Child()
    }.padding(20).alignItems(HorizontalAlign.Start)
}
}

//子组件
@Component
struct Child {
build() {
    Grandson()
}
}

//孙组件
@Component
struct Grandson {
@Consume('weight') weight: number

build() {
    Column({ space: 20 }) {
      Text(`孙组件体重值:${this.weight}`)
      Button(`孙组件体重+1`).onClick(() => {
      this.weight++
      })
    }.margin({ top: 50 })
}
}


运行结果:
https://i-blog.csdnimg.cn/direct/2ed42103c49341688d4b931019eaed79.gif
方法二:@LocalStorageProp或@LocalStorageLink

见下文先容
方法三:@StorageProp和@StorageLink

见下文先容
方法四:eventHub

见下文先容
方法五:globalThis

见下文先容
三、兄弟组件参数通报

方法一:@Provide和@Consume

利用@Provide和@Consume数据双向绑定特性,以公共的父组件为中间媒介通报数据
示例代码:
//父组件
@Entry
@Component
struct Index {
//姓名
@Provide('name') name: string ='张三'

build() {
    Column({ space: 20 }) {
      Child()
      Child2()
    }
}
}

//子组件1
@Component
struct Child {
@Consume('name') name: string
build() {
    Column({ space: 20 }) {
      Text(`姓名为${this.name}`)
    }
}
}

//子组件2
@Component
struct Child2 {
@Consume('name') name: string
build() {
    Column({ space: 20 }) {
         Button('改变姓名').onClick(()=>{
         this.name='李四'
         })
    }
}
}


运行结果:
https://i-blog.csdnimg.cn/direct/93659581f9514a4f8f0146c6fcd13916.gif
方法二:@Prop和变乱回调函数

利用父组件为中间媒介,数据先从子组件1通报到父组件,在从父组件传给组件2
示例:
//父组件
@Entry
@Component
struct Index {
//姓名
@State name: string ='张三'

build() {
    Column({ space: 20 }) {
      Child({name:this.name})
      Child2({changeAction:(name:string)=>{
      this.name=name
      }})
    }
}
}

//子组件1
@Component
struct Child {
@Prop name: string
build() {
    Column({ space: 20 }) {
      Text(`姓名为${this.name}`)
    }
}
}

//子组件2
@Component
struct Child2 {
//回调函数
private changeAction:(name:string)=>void=()=>{}
build() {
    Column({ space: 20 }) {
         Button('改变姓名').onClick(()=>{
         this.changeAction('李四')
         })
    }
}
}


运行结果:和方法一:@Provide和@Consume示例一样结果
方法三:@LocalStorageProp或@LocalStorageLink

   @LocalStorageProp和@LocalStorageLink是页面级的UI状态存储,页面内的所有组件共享一份存储数据
示例:
let storage: LocalStorage = new LocalStorage();
storage.setOrCreate('name','张三')

//父组件
@Entry(storage)
@Component
struct Index {


build() {
    Column({ space: 20 }) {
      Child()
      Child2()
    }
}
}

//子组件1
@Component
struct Child {
@LocalStorageProp('name') name:string='' //姓名
build() {
    Column({ space: 20 }) {
      Text(`姓名为${this.name}`)
    }
}
}

//子组件2
@Component
struct Child2 {
@LocalStorageLink('name') name:string='' //姓名
build() {
    Column({ space: 20 }) {
         Button('改变姓名').onClick(()=>{
         this.name='李四'
         })
    }
}
}


运行结果:和方法一:@Provide和@Consume示例一样结果
方法四:@StorageProp和@StorageLink

见下文先容
方法五:eventHub

见下文先容
方法六:globalThis

见下文先容
三、差别页面组件间数据通报

方法一:@StorageProp和@StorageLink

   @StorageProp和@StorageLink是应用全局的UI状态存储,与之对应的是全局静态类AppStorage。和进程绑定,同一进程下的所有页面共用。
示例:
第一个页面Index.ets
import { router } from '@kit.ArkUI'

AppStorage.setOrCreate('name','张三')
@Entry
@Component
struct Index {
@StorageProp('name') name:string=''

build() {
    Column({space:20}) {
       Text(`姓名为:${this.name}`)
      Button('跳转下一页').onClick(()=>{
      router.pushUrl({
          url:'pages/Second'
      })
      })

    }
    .width('100%')
    .height('100%')
    .padding({top:30})
}
}


第二个页面Second.ets
import { router } from '@kit.ArkUI'

@Entry
@Component
struct Second {
@StorageLink('name') name: string = ''

build() {
    Column({space:20}) {
      Text(`当前姓名:${this.name}`)
      Button('改变姓名值').onClick(() => {
      this.name = '李四'
      })
      Button('返回').onClick(()=>{
      router.back()
      })
    }.width('100%')
    .height('100%')
    .padding({top:30})
}
}
运行结果:
https://i-blog.csdnimg.cn/direct/ede78642e78645b3a84f35f25256a3ff.gif
方法二:eventHub

   eventHub和vue中eventBus一样使用方法也类似安卓的广播机制,通过emit发出变乱(广播)并携带参数,在有注册该变乱(on)的页面会同步吸取到该变乱的回调,
eventHub.emit():发出变乱
eventHub.on():注册变乱
eventHub.off():解绑变乱
ps:必要注意的是离开页面必须进行解绑,否则再次打开会页面会造成多次注册导致同一个变乱被多次触发回调
eventHub为Context上一个属性对象,在页面内通过getContext().eventHub获取
示例:
场景:个人信息编辑成功后关照前面页面更新个人信息
第一个页面Index.ets
import { router } from '@kit.ArkUI'

//父组件
@Entry
@Component
struct Index {
@State name: string = '张三';
@State age: number = 25

aboutToAppear(): void {
    //注册事件
    getContext().eventHub.on('userInfoChange', this.handleUserInfoChange.bind(this))
}

aboutToDisappear(): void {
    //解绑事件
    getContext().eventHub.off('userInfoChange', this.handleUserInfoChange.bind(this))
}

//用户信息修改成功回调
private handleUserInfoChange(data: Record<string, string>) {
    this.name = data.name ?? ''
    this.age = data.age ? Number(data.age) : this.age
}

build() {
    Column({ space: 20 }) {
      Text(`姓名:${this.name}`)
      Text(`年龄:${this.age}`)
      Button('修改个人信息').onClick(() => {
      router.pushUrl({
          url: 'pages/Editor'
      })
      })
    }.width('100%').padding({ top: 20 })
}
}



第二个页面:Editor.ets
import { promptAction, router } from '@kit.ArkUI'

@Entry
@Component
struct Editor {
@State name:string=''
@State age:string=''
build() {
    Column({space:15}) {
      TextInput({placeholder:'请输入姓名',text:$$this.name})
      TextInput({placeholder:'请输入年龄',text:$$this.age}).type(InputType.Number)
      Button('提交').onClick(()=>{
            getContext().eventHub.emit('userInfoChange',{
            age:this.age,
            name:this.name
            })
      promptAction.showToast({message:'修改成功'})
      router.back()
      })
    }.width('100%').height('100%').justifyContent(FlexAlign.Center).padding(20)
}
}
ps:注意this的指向,绑定回调函数必要用bind(this)
运行结果:
https://i-blog.csdnimg.cn/direct/dfcc2817f6b24d928dcad2f0e0a5ea8a.gif
eventHub在现实项目开发中非常实用,可以很轻松实现恣意差别页面或组件之间的通信。常用于一些必要革新页面操纵的场景,除了上述例子外,还可以应用于提交一个表单数据后关照前面页面革新列表数据,或者好比在支付完成后关照订单列表革新订单状态等。
ps:注意预览器和真机的差异性对eventHub支持不友好,以是如有使用eventHub功能要在真机或者模拟器测试
方法三:globalThis

   globalThis是ArkTS引擎实例内部的一个全局对象,引擎内部的UIAbility/ExtensionAbility/Page都可以使用,简朴说globalThis是一个全局对象变量,所有页面都可以直接引用,以是可以把数据存到globalThis属性上供其他页面使用
示例:
以方法eventHub示例一样功能场景用globalThis实现
第一个页面Index.ets
import { router } from '@kit.ArkUI'


//父组件
@Entry
@Component
struct Index {
@State name: string = ''
@State age: number = 0

build() {
    Column({ space: 20 }) {
      Text(`姓名:${this.name}`)
      Text(`年龄:${this.age}`)
      Button('修改个人信息').onClick(() => {
      router.pushUrl({
          url: 'pages/Editor'
      })
      })
    }
    .width('100%')
    .padding({ top: 20 })
    .onVisibleAreaChange(, async (isVisible: boolean, currentRatio: number) => {
      //页面重新加载从globalThis获取最新数据
      this.name = globalThis.name ?? '张三'
      this.age = globalThis.age ?? 25
    })

}
}



第二个页面:Editor.ets
import { promptAction, router } from '@kit.ArkUI'

@Entry
@Component
struct Editor {

build() {
    Column({space:15}) {
      TextInput({placeholder:'请输入姓名'}).onChange((value:string)=>{
      //姓名数据存储到全局globalThis
      globalThis.name=value
      })
      TextInput({placeholder:'请输入年龄'}).type(InputType.Number).onChange((value:string)=>{
      //年龄数据存储到全局globalThis
      globalThis.age=value
      })
      Button('提交').onClick(()=>{
      promptAction.showToast({message:'修改成功'})
      router.back()
      })
    }.width('100%').height('100%').justifyContent(FlexAlign.Center).padding(20)
}
}
实现结果:如方法2所示
必要注意的是globalThis全局变量数据非响应式,如有更新,必要在返回第一个页面从重新获取。
globalThis为全局变量为了制止变量污染,尽可能优先使用其他方案更换。
四、父组件调用子组件内部方法

方法提炼:子组件暴露一个回调方法,把子组件实例通过入参回传给父组件,父组件全局缓存子组件实例,在必要的时候调用子组件实例上方法。
示例:
下面实现一个这样场景需求:页面重复倒计时5秒后革新列表数据,此中列表在子组件渲染,革新动作在父组件触发
import { SwipeRefresher } from '@kit.ArkUI';


//父组件
@Entry
@Component
struct Parent {
child: Child | null = null; //子组件实例
@State count: number = 5

aboutToAppear(): void {
   setInterval(() => {
      this.count--
      if (this.count < 0) {
      //重新获取子组件数据
      this.child && this.child.getData();
      this.count = 5
      }
    }, 1000)
}

build() {
    Column({ space: 20 }) {

      Text(`倒计时:${this.count}秒`)
      Child({
      complete: (child: Child) => {
          this.child = child
      }
      })

    }.width('100%')
}
}


//子组件
@Component
struct Child {
//初始化回调
complete: (child: Child) => void = () => {
}
@State list: number[] = []
@State isLoading: boolean = false

aboutToAppear(): void {
    //把子组件实例回传给父组件
    this.complete(this)
    this.getData()
}

//模拟接口获取列表数据
getData() {
    this.isLoading = true
    let list: number[] = []
    for (let i = 1; i < 6; i++) {
      list.push(this.getRandom())
    }
    this.list = list
    setTimeout(() => {
      this.isLoading = false
    }, 1000)
}

//获取随机数
getRandom() {
    return Math.round(Math.random() * 10000)
}

build() {
    Column() {
      if (this.isLoading) {
      SwipeRefresher({
          content: '正在加载中',
          isLoading: this.isLoading
      })
      } else {
      List() {
          ForEach(this.list, (item: number, index: number) => {
            ListItem() {
            Text(item.toString()).width('100%').height(80).textAlign(TextAlign.Center).border({
                width: {
                  bottom: 1
                }
            })
            }.width('100%')
          }, (item: number, index: number) => item.toString())
      }.width('100%')
      }
    }.width('100%').height('100%')

}
}

运行结果:
https://i-blog.csdnimg.cn/direct/318711e07c724d36829e9cf31199b76d.gif
四、子组件调用父组件内部方法

方法提炼:父组件实例当参数通报给子组件,子组件缓存父组件实例,在必要的时候调用实例上方法
示例:
下面实现一个这样场景需求:父组件有一个加载中组件,对应有关闭和表现2个控制方法。子组件接口正在哀求数据时表现加载中,接口哀求完毕后关闭加载中。
import { SwipeRefresher } from '@kit.ArkUI';


//父组件
@Entry
@Component
struct Parent {
@State isLoading: boolean = false
//显示加载中
showLoading() {
    this.isLoading = true
}

//隐藏加载中
hideLoading() {
    this.isLoading = false
}

build() {
    Column({ space: 20 }) {
      if (this.isLoading) {
      SwipeRefresher({
          content: '正在加载中',
          isLoading: this.isLoading
      })
      }
      Child({
      parent: this
      })

    }.width('100%')
}
}


//子组件
@Component
struct Child {
parent: Parent | null = null

build() {
    Column() {
      Button('加载数据').onClick(() => {
      this.parent?.showLoading()
      //模拟接口请求2秒后关闭加载显示
      setTimeout(() => {
          this.parent?.hideLoading()
      }, 2000)
      })

    }.width('100%').height('100%')

}
}

运行结果:
https://i-blog.csdnimg.cn/direct/45036cf4e1d041fa8cfef4d9477f005b.gif
五、路由跳转和传参

1、通过 router.pushUrl( options: RouterOptions)实现路由跳转
RouterOptions:类型属性字段:
url:目标页面的url
params:路由跳转时要同时通报到目标页面的数据
import { router } from '@kit.ArkUI'
......
.....
router.pushUrl({
         url:'pages/Editor',
         params:{
             name:'张三',
         }
         })
2、通过router.getParams()获取路由参数

aboutToAppear(): void {
    //获取路由参数
    let params = router.getParams() as Record<string, string >
    console.log( params['name'] as string )
}
代码示例:
由页面A跳转到页面B并携带姓名和年事参数
页面A:
Index.ets
//父组件
import { router } from '@kit.ArkUI'

@Entry
@Component
struct Parent {


build() {
    Column({ space: 20 }) {
       Button('跳转下一页').onClick(()=>{
         router.pushUrl({
         url:'pages/Editor',
         params:{
             name:'张三',
             age:20
         }
         })
       })
    }.width('100%')
}
}




页面B:
Editor.ets
import { router } from '@kit.ArkUI'

@Entry
@Component
struct Editor {
@State name: string = ''//姓名
@State age: string = ''//年龄

aboutToAppear(): void {
    //获取路由参数
    let params = router.getParams() as Record<string, string | number>
    this.name = params['name'] as string ?? ''
    this.age =( params['age'] as number ?? 0).toString()
}

build() {
    Column({ space: 15 }) {
      TextInput({ placeholder: '请输入姓名', text: $$this.name })
      TextInput({ placeholder: '请输入年龄', text: $$this.age }).type(InputType.Number)
    }.width('100%').height('100%').justifyContent(FlexAlign.Center).padding(20)
}
}
运行结果:
https://i-blog.csdnimg.cn/direct/99d0cc863d51444ea0a8f44071a96cb9.gif

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: HarmonyOS Next 组件或页面之间的所有通信(传参)方法总结