HarmonyOS 鸿蒙Next中父子通信

HarmonyOS 鸿蒙Next中父子通信 不使用prop能做到父传子的通信吗?

10 回复

【解决方案】

更多关于HarmonyOS 鸿蒙Next中父子通信的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


楼主,在HarmonyOS开发中,即使不使用 @Prop 装饰器,仍有多种方式可以实现父组件向子组件传递数据。以下是常见的替代方案及实现思路:

一、使用 @Link 装饰器(双向同步)

虽然 @Link 主要用于双向数据同步,但它仍然通过属性传递数据,只是装饰器类型不同。父组件传递状态变量,子组件通过 @Link 接收并可直接修改父组件数据。

// 父组件
@Entry
@Component
struct Parent {
  @State parentData: string = "初始数据";

  build() {
    Column() {
      ChildWithLink({ parentData: $parentData }) // 使用$符号传递双向绑定
    }
  }
}

// 子组件
@Component
struct ChildWithLink {
  [@Link](/user/Link) parentData: string; // 接收父组件数据

  build() {
    Text(this.parentData)
  }
}

二、使用 @Provide@Consume(跨层级传递)

通过依赖注入实现跨层级通信,父组件提供数据(@Provide),子组件直接消费(@Consume),无需逐层传递。

// 父组件
@Entry
@Component
struct Parent {
  [@Provide](/user/Provide)("sharedData") data: string = "共享数据";

  build() {
    Column() {
      ChildComponent()
    }
  }
}

// 子组件(可以是任意层级的子孙组件)
@Component
struct ChildComponent {
  [@Consume](/user/Consume)("sharedData") childData: string; // 直接获取父组件数据

  build() {
    Text(this.childData)
  }
}

三、通过事件或回调函数

父组件将方法作为参数传递给子组件,子组件通过调用该方法触发父组件逻辑,间接传递数据(适用于动态交互场景)。

// 父组件
@Entry
@Component
struct Parent {
  @State receivedData: string = "";

  // 定义回调函数
  handleChildData = (data: string) => {
    this.receivedData = data;
  };

  build() {
    Column() {
      ChildComponent({ onDataChange: this.handleChildData })
      Text(`子组件传递的数据:${this.receivedData}`)
    }
  }
}

// 子组件
@Component
struct ChildComponent {
  private onDataChange: (data: string) => void; // 接收父组件回调函数

  build() {
    Button("传递数据给父组件")
      .onClick(() => {
        this.onDataChange("来自子组件的数据");
      })
  }
}

四、全局状态管理(AppStorage/LocalStorage)

通过全局状态管理工具(如 AppStorage)实现数据共享,父组件设置数据,子组件直接读取。

// 父组件设置数据
AppStorage.SetOrCreate("globalData", "全局数据");

// 子组件获取数据
@Component
struct ChildComponent {
  @StorageLink("globalData") childData: string;

  build() {
    Text(this.childData)
  }
}

五、事件总线(EventBus)

通过自定义事件中心实现组件间通信,父组件发布事件,子组件订阅并接收数据。

// 定义事件总线(简化示例)
class EventBus {
  static emit(event: string, data: any) { /* 发布事件逻辑 */ }
  static on(event: string, callback: (data: any) => void) { /* 订阅事件逻辑 */ }
}

// 父组件发布数据
EventBus.emit("parentToChild", "通过事件传递的数据");

// 子组件订阅数据
@Component
struct ChildComponent {
  @State data: string = "";

  onPageShow() {
    EventBus.on("parentToChild", (data: string) => {
      this.data = data;
    });
  }

  build() {
    Text(this.data)
  }
}

选择建议

  • 简单单向传递:优先使用 @Provide/@Consume 或全局状态管理。
  • 动态交互场景:通过回调函数或事件总线实现灵活通信。
  • 跨组件层级较深:依赖注入(@Provide/@Consume)或全局状态更高效。

找HarmonyOS工作还需要会Flutter的哦,有需要Flutter教程的可以学学大地老师的教程,很不错,B站免费学的哦:https://www.bilibili.com/video/BV1S4411E7LY/?p=17

在 ArkTS 中,除了 @Prop,你还可以使用 @Provide@Consume 装饰器来实现,这种方式尤其适合跨层级(例如直接从父组件传给孙子组件,而无需经过中间的子组件)的数据传递。

通俗解释

想象一下家庭聚餐:

  • 传统方式 (@Prop):就像爸爸(父组件)把一筐水果(数据)给了大儿子(直接子组件),大儿子再从中拿出一些传给小孙子(孙子组件)。大儿子是必须经过的中间人。
  • 新方式 (@Provide / @Consume):就像爸爸(父组件)在家里的公共区域(比如客厅茶几)放了一筐水果(@Provide 数据),然后小孙子(任何后代组件)可以直接跑到客厅拿水果吃(@Consume 数据)。不需要大儿子在中间传递

@Provide@Consume 就像建立了一个家族内部的“共享储物柜”,父组件把东西放进去,任何子组件或孙子组件都可以直接来取用。


具体例子:使用 @Provide@Consume

我们用一个简单的计数器例子来说明。父组件提供一个计数器,孙子组件直接消费并显示它,中间的子组件完全不需要参与数据的传递

1. 父组件 (爷爷) - 提供数据

父组件使用 @Provide 装饰器来声明一个可供后代组件使用的数据。

// Father.ets
@Entry
@Component
struct Father {
  // 关键点:使用 @Provide 装饰器,提供一个名为 "reviewVote" 的数据
  @Provide("reviewVote") reviewVotes: number = 0; // 初始值为0

  build() {
    Column() {
      // 显示父组件自己的计数器值
      Text(`爸爸的计数器: ${this.reviewVotes}`)
        .fontSize(30)
        .margin(10)

      // 一个按钮,点击后父组件的计数器会增加
      Button('爸爸点我+1')
        .onClick(() => {
          this.reviewVotes++; // 修改 @Provide 变量的值
        })
        .margin(10)

      // 引入子组件。注意:这里没有向子组件传递任何数据!
      Son() // ⬅️ 看这里,没有传递参数!

    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

2. 子组件 (爸爸) - 中间人

子组件不需要做任何和数据传递相关的事情,它只是负责布局,包含了孙子组件。

// Son.ets
@Component
struct Son {
  build() {
    Column() {
      // 子组件只显示一个文本,不关心数据
      Text('我是儿子组件,我不管数据')
        .fontSize(20)
        .margin(10)

      // 引入孙子组件
      GrandSon() // ⬅️ 这里也没有传递参数!

    }
  }
}

3. 孙子组件 - 消费数据

孙子组件使用 @Consume 装饰器来自动找到并接收父组件(爷爷)提供的同名数据。

// GrandSon.ets
@Component
struct GrandSon {
  // 关键点:使用 @Consume 装饰器,消费名为 "reviewVote" 的数据
  // 系统会自动向上查找,找到爷爷提供的 @Provide("reviewVote")
  @Consume("reviewVote") reviewVotes: number // 这里不需要初始化,会自动注入

  build() {
    Column() {
      // 显示从爷爷那里直接得到的计数器值
      Text(`孙子的计数器: ${this.reviewVotes}`)
        .fontSize(30)
        .margin(10)

      // 一个按钮,点击后也会改变计数器的值
      Button('孙子点我+1')
        .onClick(() => {
          this.reviewVotes++; // 修改 @Consume 变量的值
        })
        .margin(10)
    }
  }
}

运行效果

  1. 屏幕会显示三个部分:
    • “爸爸的计数器: 0”
    • “我是儿子组件,我不管数据”
    • “孙子的计数器: 0”
  2. 当你点击爸爸的按钮,爸爸和孙子的计数器会同时变成 1
  3. 当你点击孙子的按钮,爸爸和孙子的计数器会同时变成 2

总结

特性 @Prop @Provide / @Consume
数据流向 单向:父 → 子 双向:父 ↔️ 后代(子、孙等)
传递路径 必须通过直接子组件一层层传递 直接跨层级传递,无需中间组件参与
适用场景 简单的父子通信 复杂的多层组件通信,需要跨层级共享数据

通过@Link装饰器实现双向同步:父组件通过状态变量初始化子组件的@Link变量,建立双向绑定

// 子组件

@Component

struct ChildComponent {

  [@Link](/user/Link) data: number; // 必须由父组件初始化

  build() {

    Text(`${this.data}`)

  }

}

// 父组件

@Component

struct ParentComponent {

  @State parentData: number = 100;

  build() {

    Column() {

      ChildComponent({ data: $parentData }) // 使用$符号传递双向绑定

    }

  }

}

通过回调函数传递数据:父组件将方法通过属性传递给子组件,子组件调用该方法间接获取父组件数据

// 子组件

@Component

struct ChildComponent {

  @Prop onReceiveData: (data: string) => void;

  build() {

    Button('获取父数据').onClick(() => {

      this.onReceiveData?.(this.dataFromParent); // 调用父组件传递的方法

    })

  }

}

// 父组件

@Component

struct ParentComponent {

  @State parentMessage: string = "Hello from Parent";

  build() {

    Column() {

      ChildComponent({

        onReceiveData: (data) => { 

          console.log(data); // 通过回调获取子组件操作

        }

      })

    }

  }

}

通过事件通信(Emitter/EventHub):子组件监听事件,父组件触发事件时携带数据

// 子组件中注册监听

import emitter from '@ohos.events.emitter';

@Component

struct ChildComponent {

  onPageShow() {

    emitter.on('parentEvent', (data) => {

      console.log('收到父数据:', data);

    });

  }

}

// 父组件触发事件

@Component

struct ParentComponent {

  build() {

    Button('发送数据').onClick(() => {

      emitter.emit({

        eventId: 'parentEvent',

        data: { value: 'Data from parent' }

      });

    })

  }

}

通过@Provide@Inject依赖注入:父组件用@Provide提供数据,子组件通过@Inject注入

// 父组件

@Component

struct ParentComponent {

  [@Provide](/user/Provide)('sharedData') data: string = "Parent Data";

  build() {

    Column() {

      ChildComponent()

    }

  }

}

// 子组件

@Component

struct ChildComponent {

  [@Inject](/user/Inject)('sharedData') childData: string;

  build() {

    Text(this.childData) // 显示"Parent Data"

  }

}
  1. 还可以使用AppStorageAppStorage:应用全局的UI状态存储-管理应用拥有的状态-状态管理(V1)-学习UI范式状态管理-UI开发 (ArkTS声明式开发范式)-ArkUI(方舟UI框架)-应用框架 - 华为HarmonyOS开发者

  2. 也能结合Observed自己实现一个方法类来共享数据

[@Observed装饰器和@ObjectLink装饰器:嵌套类对象属性变化-管理组件拥有的状态-状态管理(V1)-学习UI范式状态管理-UI开发 (ArkTS声明式开发范式)-ArkUI(方舟UI框架)-应用框架 - 华为HarmonyOS开发者](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-observed-and-objectlink)

1.@link 替换@prop,实现父子双向绑定

2.emitter 事件订阅,同时还可以线程间通信

emitter.emit("calcVoice", { data: { buffer } })
aboutToAppear(): void {
   emitter.on("calcVoice", (res: emitter.EventData) => {
     this.calculateAmplitudes(res.data!["buffer"] as ArrayBuffer)
   })
}
 aboutToDisappear(): void {
    emitter.off("calcVoice")
}

3.eventhup 线程内通信

getContext().eventHub.emit('update')
aboutToAppear(): void {
    this.getUser()
    getContext().eventHub.on('update', () => {
      this.getUser()
    })
  }

HarmonyOS Next中父子通信主要通过@Provide和@Consume装饰器实现。@Provide在父组件声明状态变量并自动同步给子组件,@Consume在子组件接收并使用该状态。状态变更时双向数据绑定自动更新UI。父子组件也可通过自定义事件通信,子组件用this.$emit()触发事件,父组件通过v-on监听处理。

在HarmonyOS Next中,父组件向子组件传递数据通常推荐使用prop机制,但也可以通过其他方式实现,例如:

  1. 使用@Provide@Consume装饰器:通过跨组件层级共享状态,父组件提供数据,子组件直接消费,无需逐层传递prop。
  2. 使用全局状态管理:如AppStorage或LocalStorage存储数据,子组件直接访问,但需注意作用域和性能影响。
  3. EventHub事件总线:通过发布订阅模式传递数据,但可能增加代码耦合度。

不过,prop仍是官方推荐的标准方式,因其清晰且易于维护。其他方法需根据具体场景谨慎选择。

回到顶部