harmonyOs 实用方法(一)父组件调用子组件方法
总的来说,@Ref是解决“父组件如何直接调用子组件方法”这一问题的最常规选择。需要主动调用:首选 @Ref,简单且高效。需要同步状态:使用 @Link或 @Prop + @Event,这是鸿蒙推荐的数据驱动方式。组件设计复杂,需要对外暴露API:考虑Controller 代理模式,使代码更清晰。跨越多层或非父子组件:使用 emitter事件总线。
方案对比
| 方案 | 核心机制 | 数据流向 | 优点 | 缺点 | 推荐场景 |
|---|---|---|---|---|---|
| 1. @Ref 直接调用 | 获取子组件实例,直接调用其方法 | 父 → 子 | 简单直接,精准调用 | 破坏组件封装性,耦合度最高 | 需要主动触发子组件行为(如表单验证、动画播放等) |
| 2. @Link 双向同步 | 父组件状态变量与子组件状态变量双向绑定 | 父 ↔ 子 | 数据驱动,子组件状态变化可自动同步回父组件 | 需同时修改父子组件,适用于状态同步场景 | 子组件需要修改父组件数据并实时响应 |
| 3. Controller 代理模式 | 通过自定义控制器类封装子组件方法 | 父 → 子 | 逻辑集中,便于管理多个组件,复用性强 | 代码量较多,需要额外定义控制器类 | 需要统一管理多个子组件或提供对外API的复杂组件 |
| 4. emitter 事件总线 | 通过全局事件总线发布/订阅事件 | 任意方向 | 跨组件、跨页面通信能力强,解耦 | 事件管理复杂,需手动清理监听,有内存泄漏风险 | 非直系组件,或需要跨越多层组件通信的场景 |
详细说明与示例
1. @Ref 直接调用(最推荐)
这是实现“父调子”最标准、最直接的方式。通过在父组件中为子组件添加 @Ref 装饰器,你可以获得子组件的实例引用,从而调用其内部定义的任何公共方法。
子组件 (Child.ets)
typescript
@Component
export struct ChildComponent {
// 定义一个公共方法,供父组件调用
public childMethod() {
console.log('子组件的方法被父组件调用了');
// 在这里执行子组件的业务逻辑,例如更新UI、重置表单等
}
build() {
Column() {
Text('我是子组件')
}
}
}
父组件 (Parent.ets)
typescript
@Entry
@Component
struct ParentComponent {
// 1. 定义一个 Ref 对象,泛型为子组件类型
childRef: Ref<ChildComponent> = Ref();
// 2. 父组件的方法,调用子组件的方法
callChildMethod() {
// 通过 value? 安全地调用子组件的方法
this.childRef.value?.childMethod();
}
build() {
Column() {
// 3. 为子组件添加 ref 属性,绑定到定义的 Ref 对象
ChildComponent().ref(this.childRef)
Button('父组件调用子组件方法')
.onClick(() => {
this.callChildMethod();
})
}
}
}
2. @Link 数据双向同步
这种方式不是直接“调用”方法,而是通过改变共享数据,利用数据驱动UI的特性,“触发”子组件响应。当父组件修改了一个用 @Link 与子组件绑定的变量时,子组件会监听到变化并自动执行相应的逻辑。
子组件 (Child.ets)
typescript
@Component
export struct ChildComponent {
// 使用 @Link 装饰器,与父组件的 @State 变量建立双向绑定
@Link isActive: boolean;
// 监听 isActive 的变化
aboutToUpdate() {
if (this.isActive) {
console.log('isActive 变为 true,执行激活逻辑');
// 在这里执行激活时需要做的操作
} else {
console.log('isActive 变为 false,执行非激活逻辑');
}
}
build() {
Text(`子组件状态: ${this.isActive ? '激活' : '未激活'}`)
}
}
父组件 (Parent.ets)
typescript
@Entry
@Component
struct ParentComponent {
// 父组件自己的状态变量
@State isActive: boolean = false;
build() {
Column() {
// 使用 $ 符将父组件的状态变量传递给子组件的 @Link 变量
ChildComponent({ isActive: $isActive })
Button('激活子组件')
.onClick(() => {
// 修改父组件的状态,子组件的 @Link 变量会自动同步,并触发其 aboutToUpdate
this.isActive = true;
})
}
}
}
3. Controller 代理模式
这种方式适合需要为复杂组件提供一个清晰、可复用的API的场景。
子组件 (Child.ets)
typescript
// 1. 定义子组件的控制器类
export class ChildController {
public doSomething: () => void = () => {};
}
@Component
export struct ChildComponent {
// 2. 接收父组件传入的控制器实例
controller?: ChildController;
private handleSomething() {
console.log('子组件正在执行具体操作');
}
aboutToAppear() {
// 3. 在组件即将出现时,将内部方法绑定到控制器上
if (this.controller) {
this.controller.doSomething = this.handleSomething.bind(this);
}
}
build() {
Text('我是子组件')
}
}
父组件 (Parent.ets)
typescript
@Entry
@Component
struct ParentComponent {
// 1. 实例化控制器
private childController: ChildController = new ChildController();
callChild() {
// 2. 通过控制器调用子组件的方法
this.childController.doSomething();
}
build() {
Column() {
// 3. 将控制器实例传给子组件
ChildComponent({ controller: this.childController })
Button('通过Controller调用子组件')
.onClick(() => {
this.callChild();
})
}
}
}
总结
总的来说,@Ref 是解决“父组件如何直接调用子组件方法”这一问题的最常规选择。而在其他不同场景下,选择的原则如下:
-
需要主动调用:首选
@Ref,简单且高效。 -
需要同步状态:使用
@Link或@Prop + @Event,这是鸿蒙推荐的数据驱动方式。 -
组件设计复杂,需要对外暴露API:考虑 Controller 代理模式,使代码更清晰。
-
跨越多层或非父子组件:使用
emitter事件总线。
更多推荐



所有评论(0)