HarmonyOS应用开发实战:猫猫大作战-@Link 声明与 $val 传参、双向同步机制、@Link vs @Prop vs @Event 取舍、
前言上一篇我们用Prop把得分从父传给子 HUD——单向只读子改不了父。但有些场景子组件要直接改父的数据表单子组件改父的 username、滑块子组件改父的音量、暂停弹窗里的「继续/重开」按钮改父的 gameState。这时Prop不够用要用Link双向绑定——子改父同步父改子也同步。本篇以「猫猫大作战」暂停弹窗子组件用Link改父 gameState 为锚点把Link 声明与 $val 传参、双向同步机制、Link vs Prop vs Event 取舍、双向数据流的边界四大要点讲透。提示本系列不讲 ArkTS 基础语法与环境搭建假设你已跟完第 1–40 篇。本篇是阶段二第十一篇。一、场景拆解暂停弹窗改父 gameState回顾「猫猫大作战」PauseOverlay第 93 篇会专讲此处简提// 来源entry/src/main/ets/pages/Index.ets PauseOverlay() Builder PauseOverlay() { Column() { Text(游戏暂停) Button(继续游戏).onClick(() { this.resumeGame(); }) // ← 调父方法 Button(重新开始).onClick(() { this.clearTimers(); this.startGame(); }) Button(返回主菜单).onClick(() { this.clearTimers(); this.gameState GameState.IDLE; }) } }现在想把 PauseOverlay 拆成独立子组件PauseOverlay子组件的「继续」按钮要改父的 gameState——用LinkComponent export struct PauseOverlay { Link gameState: GameState; // 双向绑定父的 gameState Prop score: number; // 只读显示当前得分 build() { Button(继续游戏).onClick(() { this.gameState GameState.PLAYING; // ← 子直接改 Link同步到父 }) /* ... */ } } // 父 PauseOverlay({ gameState: this.$gameState, score: this.score })关键经验Link 父↔子双向引用——子改 Link 等于改父的 State父改 State 子也同步。二、Link 基本用法2.1 子组件声明 LinkComponent export struct PauseOverlay { Link gameState: GameState; // 双向绑定 Prop score: number; // 只读显示 build() { Column() { Text(游戏暂停) Text(当前得分: ${this.score}) // 读 Prop Button(继续游戏).onClick(() { this.gameState GameState.PLAYING; // 改 Link同步父 }) Button(重新开始).onClick(() { this.gameState GameState.IDLE; // 改 Link触发父切主菜单 // 注意重新开始的定时器清理由父的 Watch 或 aboutToDisappear 处理 }) Button(返回主菜单).onClick(() { this.gameState GameState.IDLE; // 改 Link }) } } }拆解片段含义Link装饰器标记「双向绑定父的 State」gameState变量名必须与父传参键名一致GameState类型与父 State 一致2.2 父组件用 $val 传参Entry Component struct Index { State gameState: GameState GameState.IDLE; State score: number 0; build() { Stack() { if (this.gameState ! GameState.IDLE) { this.GameView() } if (this.gameState GameState.PAUSED) { PauseOverlay({ gameState: this.$gameState, // ← $val 传双向引用 score: this.score // 普通值传 Prop }) } } } }关键语法this.$gameState——$前缀传「State 的引用」子组件用 Link 接收。普通this.gameState传值Propthis.$gameState传引用Link。2.3 $val 的本质this.$gameState // 引用子改会影响父 this.gameState // 值子改不影响父传递方式子装饰器数据流this.$gameStateLink双向子改父同步this.gameStateProp单向只读关键经验Link 必须用$val传引用——传普通值会编译报错或退化为 Prop。三、双向同步机制3.1 子改父同步// 子 PauseOverlay Button(继续).onClick(() { this.gameState GameState.PLAYING; // 改 Link }) // ArkUI // 1. 子改 gameState // 2. 检测到 Link 绑定父的 State gameState // 3. 同步到父 State gameState PLAYING // 4. 父的 Watch(onGameStateChange) 触发第 39 篇 // 5. 父 build() 重渲染if (gameState PAUSED) 不再渲染 PauseOverlay关键经验子改 Link 等于改父 State——会触发父的 Watch、父的重渲染、父依赖该 state 的所有组件更新。3.2 父改子同步// 父 Index this.gameState GameState.PAUSED; // 父改 State // ArkUI // 1. 父改 gameState // 2. 检测到子 PauseOverlay 的 Link gameState 绑定本 State // 3. 同步到子 Link gameState PAUSED // 4. 子 build() 重渲染如果有依赖 gameState 的显示实战经验Link 真双向——无论父改还是子改对方都同步。3.3 双向 vs 单向对比维度Prop单向Link双向子改父❌ 不回传✅ 同步父父改子✅ 同步子✅ 同步子传递语法普通值this.score引用this.$gameState适合显示型编辑型四、完整改造PauseOverlay 拆为子组件4.1 创建 PauseOverlay 子组件新建entry/src/main/ets/components/PauseOverlay.etsimport { GameState } from ./GameTypes; Component export struct PauseOverlay { Link gameState: GameState; // 双向改父 gameState Prop score: number; // 只读显示当前得分 build() { Column() { Column() { Text(⏸️) .fontSize(48) .margin({ bottom: 12 }) Text(游戏暂停) .fontSize(24) .fontWeight(FontWeight.Bold) .fontColor(#2C3E50) .margin({ bottom: 8 }) Text(当前得分: ${this.score}) // 读 Prop .fontSize(16) .fontColor(#7F8C8D) .margin({ bottom: 24 }) // 继续按钮改 Link 切回 PLAYING Button(继续游戏) .width(80%).height(48) .fontSize(17).fontWeight(FontWeight.Bold) .fontColor(#FFFFFF).backgroundColor(#2ECC71) .borderRadius(24) .margin({ bottom: 12 }) .onClick(() { this.gameState GameState.PLAYING; // ← 改 Link }) // 重新开始改 Link 切 IDLE父 Watch 处理定时器清理 Button(重新开始) .width(80%).height(48) .fontSize(17) .fontColor(#FFFFFF).backgroundColor(#F39C12) .borderRadius(24) .margin({ bottom: 12 }) .onClick(() { this.gameState GameState.IDLE; // ← 改 Link }) // 返回主菜单改 Link 切 IDLE Button(返回主菜单) .width(80%).height(48) .fontSize(17) .fontColor(#7F8C8D).backgroundColor(#ECF0F1) .borderRadius(24) .onClick(() { this.gameState GameState.IDLE; // ← 改 Link }) } .width(80%).padding(32) .backgroundColor(#FFFFFF).borderRadius(20) .shadow({ radius: 20, color: rgba(0,0,0,0.15), offsetY: 8 }) .alignItems(HorizontalAlign.Center) } .width(100%).height(100%) .backgroundColor(rgba(44, 62, 80, 0.6)) .justifyContent(FlexAlign.Center) .alignItems(HorizontalAlign.Center) } }4.2 Index 引用并 $val 传参// 来源entry/src/main/ets/pages/Index.ets改造后 import { PauseOverlay } from ../components/PauseOverlay; import { GameHUD } from ../components/GameHUD; import { Cat, CatLevel, GameConfig, CatConfig, GameState, ComboInfo } from ../components/GameTypes; import { GameEngine } from ../components/GameEngine; Entry Component struct Index { State Watch(onGameStateChange) gameState: GameState GameState.IDLE; State score: number 0; State cats: Cat[] []; State combo: ComboInfo { count: 0, multiplier: 1, lastMergeTime: 0 }; State nextCatLevel: CatLevel CatLevel.SMALL; State highScore: number 0; State gameTime: number 0; State maxCombo: number 0; State mergeCount: number 0; State highestLevel: CatLevel CatLevel.SMALL; private gameEngine: GameEngine new GameEngine(); private gameLoopTimer: number -1; private spawnTimer: number -1; private timeTimer: number -1; private readonly cols: number[] [0, 1, 2, 3, 4]; private readonly rows: number[] [0, 1, 2, 3, 4, 5, 6, 7]; // gameState 变化回调第 39 篇处理定时器清理等副作用 onGameStateChange(newVal: GameState): void { switch (newVal) { case GameState.IDLE: // 切回主菜单时清定时器重新开始/返回主菜单都会走这里 this.clearTimers(); break; case GameState.GAME_OVER: // 结束时清定时器 this.clearTimers(); break; } } /* startGame / pauseGame / resumeGame / endGame / handleColumnClick / clearTimers / formatTime / aboutToDisappear 等略 */ build() { Stack() { if (this.gameState GameState.IDLE) { this.MainMenuView() } else { this.GameView() } // 改造用子组件替代 Builder PauseOverlay if (this.gameState GameState.PAUSED) { PauseOverlay({ gameState: this.$gameState, // ← $val 传双向引用 score: this.score // 普通值传 Prop }) } if (this.gameState GameState.GAME_OVER) { this.GameOverOverlay() } } .width(100%).height(100%) } /* GameView / MainMenuView / GameOverOverlay / StatItem 筥略 */ }4.3 改造要点原写法改造后效果this.resumeGame()调父方法this.gameState PLAYING改 Link子直接改父 statethis.clearTimers(); this.startGame()父处理this.gameState IDLE改 Link父 Watch 处理清理PauseOverlay 在 Index 内独立PauseOverlay.ets分文件维护关键经验Link 父 Watch 是「子改父 state父处理副作用」的标准模式——子只改 state副作用清定时器、播音效由父 Watch 集中处理。五、Link vs Prop vs Event 三选5.1 三种跨组件数据流完整对比装饰器方向读写传递语法适合Prop父→子只读this.score显示型HUD、卡片Link父↔子双向this.$gameState编辑型表单、弹窗按钮Event子→父事件回调this.onScoreAdd触发父方法按钮点击5.2 场景对照// 场景 1HUD 显示得分 → Prop GameHUD({ score: this.score }) // 场景 2暂停弹窗改 gameState → Link PauseOverlay({ gameState: this.$gameState }) // 场景 3子按钮点击让父加 10 → Event GameHUD({ score: this.score, onScoreAdd: (d) this.score d })5.3 Link vs Event 的细微取舍「子改父 state」既可以用 Link 直接改也可以用 Event 回调让父改// 方式 ALink 直接改 Component struct PauseOverlay { Link gameState: GameState; build() { Button(继续).onClick(() { this.gameState GameState.PLAYING; // 直接改 }) } } // 方式 BEvent 回调父改 Component struct PauseOverlay { Event onResume: () void; build() { Button(继续).onClick(() { this.onResume(); // 回调父父改 gameState }) } } // 父PauseOverlay({ onResume: () { this.gameState GameState.PLAYING; } })方式优点缺点Link 直接改简洁、少一层回调子要懂父 state 语义Event 回调子不耦合父 state多一层回调代码关键经验「子改父单一 state」用 Link 更简洁——「子触发父复杂逻辑」用 Event 更解耦。六、踩坑提示6.1 忘用 $val 传引用// ❌ 错误传普通值Link 退化为 Prop 或报错 PauseOverlay({ gameState: this.gameState }) // 传值不是引用 // ✅ 正确$val 传引用 PauseOverlay({ gameState: this.$gameState }) // $val 传引用6.2 Link 类型与父 State 不一致// 父 State gameState: GameState GameState.IDLE; // ❌ 错误子 Link 声明 string Link gameState: string; // ✅ 正确类型一致 Link gameState: GameState;6.3 子改 Link 期望触发父方法// ❌ 错误子改 Link 期望自动调父的 resumeGame() this.gameState GameState.PLAYING; // 只改 state不调方法 // ✅ 正确父用 Watch 监听 state 变化在回调里调方法 // 父 State Watch(onGameStateChange) gameState: GameState GameState.IDLE; onGameStateChange(newVal: GameState): void { if (newVal GameState.PLAYING) { this.resumeGame(); // 父在 Watch 里调方法 } } ### 6.4 Link 对象改内部属性不触发 ts // ❌ 错误改 Link 对象内部属性不触发双向同步 this.combo.count 5; // Link 也是浅观察 // ✅ 正确重新赋值整个对象 this.combo { count: 5, multiplier: 3, lastMergeTime: Date.now() };七、调试技巧console.info在子改 Link 后log 父的this.gameState追是否同步。子改了父没变排查检查是否用$val传引用检查类型是否一致检查对象是否整体赋值。父 Watch 没触发排查检查父 State 是否有 Watch检查回调字符串是否拼错。DevEco ArkUI Inspector查看组件树和 Link 绑定关系。八、性能与最佳实践编辑型子组件用 Link——弹窗按钮改父 state、表单输入改父数据。$val 传引用——普通值传 Prop引用传 Link。类型与父 State 一致——number 配 numberGameState 配 GameState。子改 Link 父 Watch 处理副作用——子只改 state副作用由父集中处理。Link 对象整体赋值才同步——和 State/Prop 一样浅观察。「子改单一 state」用 Link「子触发父复杂逻辑」用 Event——按耦合度选。总结本篇我们从 Link 双向绑定切入掌握了**Link 声明与val 传引用**、**双向同步机制子改父同步父改子同步**、**Link 父 Watch 标准模式**、**Link vs Prop vs Event 取舍**四大要点并给出了 PauseOverlay 拆为子组件的完整改造代码。核心要点**val 传引用才双向子改 Link 触发父 Watch编辑型用 Link 显示型用 Prop对象整体赋值才同步**。下一篇我们将拆解 Provide/Consume——跨层隐式共享。如果这篇文章对你有帮助欢迎点赞、收藏⭐、关注你的支持是我持续创作的动力相关资源「猫猫大作战」项目源码本仓库entry/src/main/ets/pages/Index.ets、entry/src/main/ets/components/PauseOverlay.etsArkUI Link 双向绑定官方指南ArkUI 状态管理概述ArkUI 组件化与数据流最佳实践开源鸿蒙跨平台社区HarmonyOS 开发者官方文档首页系列索引本仓库articles/INDEX.md

相关新闻

影刀RPA实战教程:7个真实案例带你从入门到独立开发

影刀RPA实战教程:7个真实案例带你从入门到独立开发

影刀RPA实战教程:7个真实案例带你从入门到独立开发 教程看了不少,一到自己写就无从下手。这很正常——RPA是做的技能,不是看的技能。这篇直接用7个真实案例串起来,从简单到复杂,每个案例都能直接拿去用或者改成你自己…

2026/7/28 3:16:51 阅读更多 →
基于行空板与图形化Python的健身打卡追踪器设计与实现

基于行空板与图形化Python的健身打卡追踪器设计与实现

1. 项目缘起:从“想健身”到“能坚持”的痛点每次看到别人晒出完美的健身数据,你是不是也心血来潮,下载了一堆健身App,办了健身卡,结果没几天就因为各种“忘了”、“累了”、“没动力”而放弃?我太懂这种感…

2026/7/28 3:15:51 阅读更多 →
电熨斗改造回流焊台:低成本实现精准温控的电子焊接方案

电熨斗改造回流焊台:低成本实现精准温控的电子焊接方案

1. 项目概述:当电熨斗遇上电子焊接如果你玩过电子DIY,或者尝试过维修一些小型电路板,那你一定对“回流焊”这个词不陌生。它听起来像是工厂里大型设备才能干的活儿,但实际上,它的核心原理简单到令人惊讶——就是通过精…

2026/7/28 3:15:51 阅读更多 →

最新新闻

nRPC快速上手:10分钟搭建你的第一个NATS RPC服务

nRPC快速上手:10分钟搭建你的第一个NATS RPC服务

nRPC快速上手:10分钟搭建你的第一个NATS RPC服务 【免费下载链接】nrpc nRPC is like gRPC, but over NATS 项目地址: https://gitcode.com/gh_mirrors/nr/nrpc nRPC是一个基于NATS的RPC框架,类似于gRPC但使用NATS作为传输层。本文将带你快速上手…

2026/7/28 3:27:55 阅读更多 →
革命性百度贴吧个性化定制模块:TiebaTS重塑你的浏览体验

革命性百度贴吧个性化定制模块:TiebaTS重塑你的浏览体验

革命性百度贴吧个性化定制模块:TiebaTS重塑你的浏览体验 【免费下载链接】TiebaTS 提供修改百度贴吧底栏等个性化功能。An Xposed module for Baidu Tieba with personalized functions. 项目地址: https://gitcode.com/gh_mirrors/ti/TiebaTS TiebaTS是一款…

2026/7/28 3:27:55 阅读更多 →
RS485转UART通信实战:从硬件连接到Modbus协议解析

RS485转UART通信实战:从硬件连接到Modbus协议解析

1. 项目概述:从串口协议到气象数据采集最近在做一个环境监测的小项目,需要同时采集风速和风向数据。手头正好有一个RS485接口的风速风向仪,但主控板用的是Arduino Uno和micro:bit。这俩板子自带的都是UART(也就是常说的串口&#…

2026/7/28 3:27:55 阅读更多 →
如何高效管理坎巴拉太空计划模组:CKAN终极指南

如何高效管理坎巴拉太空计划模组:CKAN终极指南

如何高效管理坎巴拉太空计划模组:CKAN终极指南 【免费下载链接】CKAN The Comprehensive Kerbal Archive Network 项目地址: https://gitcode.com/gh_mirrors/cka/CKAN 还在为《坎巴拉太空计划》模组安装的繁琐流程而头疼吗?CKAN作为KSP社区最受欢…

2026/7/28 3:27:55 阅读更多 →
数据立方体优化:OLAP性能提升策略与实践

数据立方体优化:OLAP性能提升策略与实践

1. 数据立方体在OLAP中的核心价值 在商业智能和数据分析领域,数据立方体(Data Cube)是OLAP(联机分析处理)系统的核心数据结构。它通过预计算和多维聚合的方式,将海量数据转化为可快速分析的形态。想象一下&…

2026/7/28 3:27:55 阅读更多 →
环结构电磁场多极子分解原理与COMSOL实现

环结构电磁场多极子分解原理与COMSOL实现

1. 项目概述:环结构电磁场的多极子分解原理在电磁场仿真领域,环结构作为一种基础却重要的几何构型,广泛存在于电感器、天线设计和磁共振系统中。传统仿真往往止步于场分布的可视化,而多极子分解技术为我们打开了理解电磁场本质的新…

2026/7/28 3:26:54 阅读更多 →

日新闻

告别臃肿!3步让你的暗影精灵笔记本重获新生

告别臃肿!3步让你的暗影精灵笔记本重获新生

告别臃肿!3步让你的暗影精灵笔记本重获新生 【免费下载链接】OmenSuperHub Control Omen laptop performance, fan speeds, and keyboard lighting, and unlock power limits. 项目地址: https://gitcode.com/gh_mirrors/om/OmenSuperHub 你是否也曾为官方Om…

2026/7/28 0:00:43 阅读更多 →
RAG必踩坑!财报法规检索不准?这款开源工具让答案浮出水面,准确率飙升98.7%!

RAG必踩坑!财报法规检索不准?这款开源工具让答案浮出水面,准确率飙升98.7%!

做 RAG 的人应该都踩过这个致命的坑:把几百页的财报、法规、技术手册扔给向量库,问一个具体问题,搜出来的全是沾边但没用的内容 —— 关键信息要么被硬切块拆碎了,要么藏在几十条结果的最下面。语义相似≠真正相关,这个…

2026/7/28 0:00:43 阅读更多 →
抖音视频文案提取工具全指南:免费2026版、手机App、在线工具一网打尽

抖音视频文案提取工具全指南:免费2026版、手机App、在线工具一网打尽

2026年做短视频运营,从抖音上扒文案早就不是偷偷抄笔记的事了。我刚开始做内容的时候,每天刷半小时抖音,手动把爆款视频的口播敲进备忘录,一条2分钟的视频得花十来分钟,碰到语速快的还要反复回听。后来试了一圈工具&am…

2026/7/28 0:00:43 阅读更多 →

周新闻

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 数据集6000张 完整源码已标注数据集训练好的模型环境配置教程程序运行说明文档,可以直接使用!系统支持图片、视频、摄像头等多种方式检测裂缝,功能强大实用。 1数据集6000张 8各类别

2026/7/27 4:33:59 阅读更多 →
深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

pubg数据集 精选原图1.42万数据 1.49万标签 无任何重复、算法增强或冗余图像! pubg绝地求生目标检测数据集 1分类:e_body,14905个标签,txt格式 共计14244张图,99%为640*640尺寸图像 适合yolo目标检测、AI训练关键词&am…

2026/7/27 6:31:56 阅读更多 →
Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex检测数据集数据集详情检测类别: allies enemy tag图片总量:7247张训练集:5139张验证集:1425张测试集:683张标注状态:全部已标注,即拿即用数据格式:支持YOLO格式及其他格式&#…

2026/7/27 4:01:12 阅读更多 →

月新闻