HarmonyOS应用开发实战:萌宠日记 - 回调函数模式

前言

萌宠日记 中,页面间数据传递 是一个核心需求。当用户在 首页 点击"宠物档案"时,需要通知 Index 父组件 执行 NavPathStack.pushPath 跳转到子页面。这种 子 → 父 的通信方式,我们采用了 回调函数模式 --- 父组件通过属性传入 lambda 函数,子组件在合适的时机调用。

本文将从 萌宠日记 的回调函数实践出发,深入解析父子组件通信的几种模式,以及回调函数的设计原则和最佳实践。

一、回调函数模式概述

1.1 什么是回调函数模式

回调函数模式是 父组件将函数作为属性传递给子组件,子组件在需要时调用该函数,从而触发父组件中的逻辑。

复制代码
父组件(Index)                  子组件(HomePage)
    │                               │
    │  传递 lambda 回调函数 ──────→  │
    │   (onNavigateToPetProfile)     │
    │                               │
    │                               │  用户点击"档案"
    │                               │
    │  ←── 调用回调函数 ──────────  │
    │  (执行 pushPath 跳转)          │

1.2 萌宠日记的回调实现

typescript 复制代码
// Index.ets --- 父组件传递回调函数
HomePage({
  onNavigateToPetProfile: () => {
    this.homeStack.pushPath({ name: 'petProfile' })
  },
  onNavigateToTimeline: () => {
    this.homeStack.pushPath({ name: 'timeline' })
  },
  onNavigateToCommunity: () => {
    this.homeStack.pushPath({ name: 'community' })
  }
})

提示 :回调函数是闭包(lambda),它可以捕获父组件作用域中的 this.homeStack,从而在子组件中触发父组件的导航逻辑。这是 ArkTS 中实现"子组件通知父组件"的标准模式。

二、回调函数的定义

2.1 属性声明

typescript 复制代码
// HomePage.ets --- 定义可选的回调函数属性
export struct HomePage {
  // 使用可选属性(?),表示可以不传
  onNavigateToPetProfile?: () => void
  onNavigateToTimeline?: () => void
  onNavigateToCommunity?: () => void
}

2.2 回调签名设计

回调函数 签名 说明
onNavigateToPetProfile () => void 导航到宠物档案页
onNavigateToTimeline () => void 导航到成长时间轴
onNavigateToCommunity () => void 导航到社区发现

2.3 可选 vs 必选

typescript 复制代码
// 可选回调(推荐):使用 ? 标记,调用前判空
export struct HomePage {
  onNavigateToPetProfile?: () => void  // 可选

  onClickProfile(): void {
    if (this.onNavigateToPetProfile) {  // 判空
      this.onNavigateToPetProfile()
    }
  }
}

// 必选回调:不标记 ?,但必须传值
export struct HomePage {
  onNavigateToPetProfile: () => void = () => {}  // 默认空函数
}

三、回调函数的调用

3.1 调用时机

typescript 复制代码
// HomePage.ets --- 在用户交互时调用回调
build() {
  // ...
  // 编辑按钮点击
  Text('✏️')
    .fontSize(20)
    .fontColor('#F5A623')
    .onClick(() => {
      if (this.onNavigateToPetProfile) {
        this.onNavigateToPetProfile()  // 触发父组件导航
      }
    })
  // ...
}

3.2 安全调用模式

typescript 复制代码
// 安全调用模式 --- 调用前判空
if (this.onNavigateToPetProfile) {
  this.onNavigateToPetProfile()
}

// 或者使用可选链调用(ES2020+)
this.onNavigateToPetProfile?.()

四、完整通信链路

4.1 从点击到跳转

复制代码
用户点击首页的"编辑"按钮(✏️)
    ↓
HomePage.onClick 触发
    ↓
回调函数调用:this.onNavigateToPetProfile?.()
    ↓
Index 中传入的 lambda 执行
    ↓
this.homeStack.pushPath({ name: 'petProfile' })
    ↓
Navigation 渲染 NavDestination → PetProfilePage

4.2 代码链路

typescript 复制代码
// 1. 父组件定义回调(Index.ets)
HomePage({
  onNavigateToPetProfile: () => {
    this.homeStack.pushPath({ name: 'petProfile' })
  }
})

// 2. 子组件声明属性(HomePage.ets)
export struct HomePage {
  onNavigateToPetProfile?: () => void
}

// 3. 子组件调用回调(HomePage.ets 中用户点击时)
Text('✏️')
  .onClick(() => {
    this.onNavigateToPetProfile?.()
  })

五、多个回调的组织

5.1 批量传递

typescript 复制代码
// Index.ets --- 一次性传递多个回调
HomePage({
  onNavigateToPetProfile: () => {
    this.homeStack.pushPath({ name: 'petProfile' })
  },
  onNavigateToTimeline: () => {
    this.homeStack.pushPath({ name: 'timeline' })
  },
  onNavigateToCommunity: () => {
    this.homeStack.pushPath({ name: 'community' })
  }
})

5.2 回调分组

typescript 复制代码
// 将回调分组到一个接口中
interface HomePageCallbacks {
  onNavigateToPetProfile: () => void
  onNavigateToTimeline: () => void
  onNavigateToCommunity: () => void
}

// 使用接口定义属性
export struct HomePage {
  callbacks?: HomePageCallbacks
}

// 调用时
this.callbacks?.onNavigateToPetProfile()

六、带参数的回调

6.1 传递数据

typescript 复制代码
// 带参数的回调定义
export struct HomePage {
  onNavigateToPetProfile?: (petId: string) => void
}

// 调用时传递参数
.onClick(() => {
  this.onNavigateToPetProfile?.('123')
})

// 父组件接收参数
HomePage({
  onNavigateToPetProfile: (petId: string) => {
    this.homeStack.pushPath({
      name: 'petProfile',
      param: { petId: petId }
    })
  }
})

6.2 参数类型

回调参数类型 说明 示例
string 字符串标识 petId, diaryId
number 数字索引 selectedIndex
Object 复杂对象 { id: '123', name: '豆豆' }
void 无参数 仅触发动作

7.1 两种通信方式

对比维度 回调函数 @Link 双向绑定
通信方向 子 → 父 父 → 子 / 双向
数据流 事件流 状态流
触发方式 主动调用 自动同步
适用场景 导航、事件通知 数据共享、状态同步
类型安全 强类型 强类型

7.2 选择建议

场景 推荐方式 理由
子组件通知父组件跳转 回调函数 事件驱动,语义清晰
子组件需要父组件数据 @Link 数据自动同步
子组件修改父组件数据 @Link 双向绑定
子组件触发父组件某个操作 回调函数 解耦,职责单一

八、回调函数的调试

8.1 调试技巧

typescript 复制代码
// 在回调中添加日志
HomePage({
  onNavigateToPetProfile: () => {
    console.log('[Nav] Navigating to pet profile')
    this.homeStack.pushPath({ name: 'petProfile' })
  }
})

// 在子组件中追踪回调
if (this.onNavigateToPetProfile) {
  console.log('[Callback] onNavigateToPetProfile is defined')
  this.onNavigateToPetProfile()
} else {
  console.warn('[Callback] onNavigateToPetProfile is undefined')
}

8.2 常见问题排查

问题 原因 解决方案
点击无反应 回调未传递 检查父组件是否传入了回调
回调执行错误 回调中逻辑异常 检查回调函数中的代码
多次触发 重复绑定事件 检查是否多次绑定了 onClick
回调未定义 属性声明为必选但未传值 使用可选属性 ? 或提供默认值

九、回调设计原则

9.1 命名规范

typescript 复制代码
// 推荐的命名模式
onNavigateToPetProfile  // on + 动作 + 目标
onSaveDiary             // on + 动作 + 对象
onDeleteRecord          // on + 动作 + 对象
onRefreshData           // on + 动作 + 对象
onError                 // on + 事件

9.2 设计原则

有序列表 --- 回调函数设计的 5 个原则:

  1. 命名清晰 :以 on 开头,描述触发的事件
  2. 参数简洁:参数数量尽量少(≤3 个),避免复杂对象
  3. 可选安全 :使用 ? 标记可选回调,调用前判空
  4. 单一职责:一个回调对应一个动作,不要合并多个逻辑
  5. 文档注释:使用 JSDoc 注释说明回调的触发时机和参数

十、扩展:EventHub 事件总线

10.1 跨组件事件

typescript 复制代码
// 使用 EventHub 实现跨组件通信
// 发送事件
this.context.eventHub.emit('diarySaved', { diaryId: '123' })

// 接收事件
this.context.eventHub.on('diarySaved', (data: DiaryData) => {
  console.log('Diary saved:', data.diaryId)
})

10.2 回调 vs EventHub 对比

对比维度 回调函数 EventHub
通信范围 父子组件 任意组件
耦合度 低(需显式传递) 更低(事件解耦)
类型安全 强类型 弱类型
调试难度 容易 较难追踪
适用场景 父子组件通信 跨层级、跨页面通信

总结

本文从 萌宠日记回调函数模式 出发,深入解析了父子组件间数据传递的实现:

  1. 回调模式:父组件通过属性传入 lambda,子组件在适当时机调用
  2. 属性声明 :使用 ? 标记可选回调,确保空安全
  3. 调用时机:在用户交互事件中触发回调
  4. 安全调用if (this.callback) { this.callback() } 模式
  5. 带参回调:传递参数实现数据通信
  6. 与 @Link 对比:事件驱动 vs 状态驱动
  7. 设计原则:命名规范、参数简洁、单一职责
  8. EventHub:跨组件事件总线的扩展方案

回调函数模式是 ArkTS 中实现父子组件通信的最直接方式,也是萌宠日记导航架构中页面间通信的核心机制。

下一篇我们将深入 页面间数据传递 --- 参数化路由,解析通过 NavPathStack 传递路由参数的实现。

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:

相关推荐
Conan在掘金4 小时前
鸿蒙报错速查:Cannot find name 'image',忘 import 编译就炸,根因 + 真解法
后端
雪隐4 小时前
个人电脑玩AI-13让5060 Ti给你打工——我用 0.9B 小模型终结了"谁来记会议纪要"这个世纪难题
前端·人工智能·后端
无名之辈J4 小时前
Ai开发
后端
Conan在掘金4 小时前
�鸿蒙报错速查:arkts-strict-typing 函数返回值类型必须显式,忘标就炸,根因 + 真解法
后端
半个落月4 小时前
用 LangChain JS 做可控写作实验:理解温度参数、提示词与异步调用
javascript·人工智能·后端
爱勇宝4 小时前
《道德经》第 7 章:真正厉害的领导者,不抢主角
前端·后端·程序员
用户208046804564 小时前
Python3 条件控制新手实战指南
后端
HONG````5 小时前
HarmonyOS ArkUI RelativeContainer 相对定位布局:ID 锚点与 alignRules 实战
后端
Conan在掘金5 小时前
�鸿蒙报错速查:Cannot find name 'AppStorageV2',忘 import 编译就炸,根因 + 眜解法
后端