HarmonyOS APP《画伴梦工厂》开发第4篇:@Link、@Prop 与 @StorageLink——组件间数据通信

📅 2026/6/30 2:19:45
HarmonyOS APP《画伴梦工厂》开发第4篇:@Link、@Prop 与 @StorageLink——组件间数据通信
第1.4篇Link、Prop 与 StorageLink——组件间数据通信难度⭐⭐ 进阶前置知识1.3 State 与状态管理涉及源文件CreationComponents.ets、BreakpointSystem.ets、Index.ets、RecognitionWaitingPage.ets、PhotoRecognitionPage.ets、FreeDoodlePage.ets在上一篇中我们学习了用State管理组件内部状态。但在真实应用中数据很少只在一个组件内闭环——它需要在父子组件之间、兄弟组件之间、甚至跨页面之间共享和同步。HarmonyOS ArkUI 为这些场景提供了三种装饰器Prop、Link和StorageLink。本文将通过「画伴梦工厂」项目的真实代码带你掌握它们的核心区别与使用场景。1. 三种装饰器概览装饰器数据流方向适用场景同步机制Prop父 → 子单向子组件接收父组件传入的只读/可覆写初始值父变则子变子变不影响父Link父 ↔ 子双向父子组件共享同一状态任一方修改都同步数据引用共享实时同步StorageLink任意组件 ↔ AppStorage跨组件/跨页面多页面共享全局状态如主题、断点、用户信息通过 AppStorage 代理同步一句话总结Prop父组件告诉子组件一个值子组件可以自己改着玩但不影响父组件。Link父子组件共享一个值谁改都会通知对方。StorageLink任何组件都能读写一个全局值适合跨页面状态。2. Link 双向绑定父子组件状态同步在「画伴梦工厂」中PhotoRecognitionPage父组件和PhotoRecognitionComponent子组件之间通过Link实现了紧密的状态同步。2.1 子组件声明 Link在CreationComponents.ets中子组件用Link声明需要从父组件获取的共享状态Componentexportstruct PhotoRecognitionComponent{LinkgenerationProgress:number;// 生成进度LinknoticeText:string;// 通知文本// 组件内部通过 State 管理自身状态StateprivatehasPhoto:booleanfalse;Stateprivaterecognizing:booleanfalse;StateprivateactiveStep:number0;StateprivatecapturedImageUri:string;// 在方法中可以直接修改 Link 变量privatecapturePhoto(uri:string,sourceLabel:string拍照图片){// ...this.generationProgress35;// ← 修改同步到父组件this.noticeText已采集画作可以直接生成动画;}}同理FreeDoodleComponent声明了更多的Link变量Componentexportstruct FreeDoodleComponent{LinkselectedTool:number;LinkselectedColor:number;LinkbrushSize:number;LinkgenerationProgress:number;LinknoticeText:string;LinkisDrawingOnCanvas:boolean;// ...}2.2 父组件通过$语法传递引用父组件PhotoRecognitionPage.ets在自己的State中定义状态然后通过$前缀将引用传给子组件EntryComponentstruct PhotoRecognitionPage{StateprivategenerationProgress:number0;StateprivatenoticeText:string;build(){Scroll(){Column(){// $ 符号将 State 变量转为 Link 引用PhotoRecognitionComponent({generationProgress:$generationProgress,noticeText:$noticeText})}}}}FreeDoodlePage的用法类似FreeDoodleComponent({selectedTool:$selectedTool,selectedColor:$selectedColor,brushSize:$brushSize,generationProgress:$generationProgress,noticeText:$noticeText,isDrawingOnCanvas:$isDrawingOnCanvas})关键点$variableName是 ArkUI 中获取State变量引用的语法传递给子组件的Link后双方操作的是同一个数据源。2.3 实际效果当用户在PhotoRecognitionComponent中点击「生成动画」时子组件修改this.generationProgress 100父组件PhotoRecognitionPage中的generationProgress同步更新父组件 UI 中的Progress组件自动刷新// PhotoRecognitionPage build 中的进度条Progress({value:this.generationProgress,total:100,type:ProgressType.Linear})这就是Link双向绑定的威力——子组件操作状态父组件的 UI 立即响应。3. Prop 单向传值父传子子可改写但不影响父Prop与Link的核心区别在于同步方向父组件更新 →Prop变量同步更新 ✅子组件修改Prop变量 →不同步回父组件 ❌虽然「画伴梦工厂」项目中未直接使用Prop但我们可以通过Builder的参数传递模式来理解单向传值的概念详见 1.5 篇。// 这相当于单向传值模式——父组件传参进来子 UI 片段使用BuilderprivatePill(text:string,active:booleanfalse){Text(text).fontSize(12).fontColor(active?#FFFFFF:#6F7590).backgroundColor(active?this.brandPurple:#F0F1F8).borderRadius(14)}何时用 Prop当子组件只需要读父组件的值或者子组件想有自己的副本但不影响父组件时。比如一个仅展示用的信息卡片。4. StorageLink 应用级状态跨组件、跨页面共享如果说Link解决的是父子组件通信那StorageLink解决的是跨页面/跨组件的全局状态共享。它通过 ArkUI 的AppStorage实现。4.1 注册断点系统在Index.ets中通过StorageLink声明一个应用级状态变量EntryComponentstruct Index{StorageLink(mainBreakpoint)currentBreakpoint:stringmd;// ↑ 键名 ↑ 默认值// ...privatereadonlybreakpointSystem:BreakpointSystemnewBreakpointSystem(mainBreakpoint);}4.2 BreakpointSystem 写入 AppStorageBreakpointSystem.ets监听媒体查询变化当断点改变时通过AppStorage.set()更新全局状态exportclassBreakpointSystem{privatecurrentBreakpoint:stringmd;privatereadonlybreakpointId:string;constructor(breakpointId:string){this.breakpointIdbreakpointId;}publicregister(uiContext:UIContext):void{this.breakpoints.forEach((breakpoint,index){letcondition:string;if(indexthis.breakpoints.length-1){condition(${breakpoint.size}vpwidth);}else{condition(${breakpoint.size}vpwidth${this.breakpoints[index1].size}vp);}breakpoint.mediaQueryListeneruiContext.getMediaQuery().matchMediaSync(condition);breakpoint.mediaQueryListener.on(change,(mediaQueryResult){if(mediaQueryResult.matches){this.updateCurrentBreakpoint(breakpoint.name);}});});}privateupdateCurrentBreakpoint(breakpoint:string):void{if(this.currentBreakpoint!breakpoint){this.currentBreakpointbreakpoint;// 写入 AppStorage所有 StorageLink(mainBreakpoint) 自动更新AppStorage.setstring(this.breakpointId,this.currentBreakpoint);}}}4.3 Index.ets 响应断点变化当AppStorage中的mainBreakpoint发生变化时Index.ets中的currentBreakpoint自动更新驱动 UI 响应式布局// 根据 currentBreakpoint 返回不同的间距值privatepageEdge():number{returnnewBreakPointTypenumber({sm:12,md:24,lg:36,xl:56}).getValue(this.currentBreakpoint);}// 根据断点返回不同的面板宽度privatepanelWidth():string{returnnewBreakPointTypestring({sm:90%,md:86%,lg:74%,xl:64%}).getValue(this.currentBreakpoint);}// 底部栏宽度也响应断点privatebottomBarWidth():string{returnnewBreakPointTypestring({sm:86%,md:72%,lg:58%,xl:48%}).getValue(this.currentBreakpoint);}// Hero 区域高度privateheroHeight():number{returnnewBreakPointTypenumber({sm:420,md:430,lg:460,xl:480}).getValue(this.currentBreakpoint);}4.4 数据流全景设备窗口变化 ↓ BreakpointSystem.register() 监听媒体查询 ↓ 断点触发 → updateCurrentBreakpoint() ↓ AppStorage.set(mainBreakpoint, lg) ↓ Index.ets StorageLink(mainBreakpoint) 自动更新 ↓ pageEdge() / panelWidth() / heroHeight() 重新计算 ↓ UI 响应式刷新这种模式下任何页面只要用StorageLink(mainBreakpoint)声明变量就能自动感知断点变化实现真正的响应式设计。4.5 StorageLink 的其他用途在 Index.ets 的aboutToAppear中还展示了AppStorage.get和AppStorage.setOrCreate的用法aboutToAppear(){// 从 AppStorage 读取跨页面传递的参数constlaunchTabAppStorage.getstring(launchTab);if(launchTabworks){constlaunchWorkIndexAppStorage.getnumber(launchWorkIndex);if(typeoflaunchWorkIndexnumber){this.openWorkDetail(launchWorkIndex);}AppStorage.setOrCreatestring(launchTab,);AppStorage.setOrCreatenumber(launchWorkIndex,-1);}}这种方式适合页面间传递启动参数比 Router 传参更灵活——即使页面还未创建数据也不会丢失。5. 页面间数据传递Router getParams()除了StorageLink页面间还有一种常用的传参方式Router 参数传递。5.1 发起方pushUrl 携带参数在CreationComponents.ets中组件通过getUIContext().getRouter().pushUrl()跳转并传参privatenavigateToGeneration(){this.getUIContext().getRouter().pushUrl({url:pages/RecognitionWaitingPage,params:{source:图片生成,workSource:photo,prompt:this.buildVideoPrompt(),imageUri:this.capturedImageUri,coverUri:this.capturedImageUri}});}FreeDoodleComponent也是同样的模式this.getUIContext().getRouter().pushUrl({url:pages/RecognitionWaitingPage,params:{source:自由涂鸦,workSource:doodle,prompt:把这张儿童涂鸦变成温暖的短动画保留粗笔触和明亮色块,imageUri:imageUri,coverUri:imageUri}});5.2 接收方aboutToAppear getParams()在RecognitionWaitingPage.ets中通过aboutToAppear生命周期方法接收参数interfaceWaitingParams{source?:string;workSource?:string;prompt?:string;imageUri?:string;coverUri?:string;recognitionResult?:string;}EntryComponentstruct RecognitionWaitingPage{Stateprivatesource:string画作识别;StateprivateworkSource:stringphoto;Stateprivateprompt:string;StateprivateimageUri:string;StateprivatecoverUri:string;aboutToAppear(){constparamsthis.getUIContext().getRouter().getParams()asWaitingParams;if(paramsparams.source){this.sourceparams.source;}if(paramsparams.workSource){if(params.workSourcedoodle){this.workSourcedoodle;}elseif(params.workSourceai-chat){this.workSourceai-chat;}else{this.workSourcephoto;}}if(paramsparams.prompt){this.promptparams.prompt;}if(paramsparams.imageUri){this.imageUriparams.imageUri;}if(paramsparams.coverUri){this.coverUriparams.coverUri;}this.startGeneration();}}5.3 Router 与 StorageLink 的选择传递方式适用场景优点缺点Router params页面跳转时的上下文数据职责清晰一次性传递仅跳转时可用返回时需重新传StorageLink / AppStorage全局状态、跨页面共享随时读写全局生效需要管理键名避免冲突Link父子组件实时同步强类型编译期检查仅限父子关系6. 回调模式通过参数传递函数实现反向通信Builder 除了构建 UI还可以通过参数传递回调函数实现子组件到父组件的反向通信。虽然这不是严格意义上的Prop/Link但在 ArkUI 中是一种常见的通信模式。在Index.ets中NavIcon通过onClick回调父组件的方法BuilderprivateNavIcon(index:number){Column(){Text(NAV_ICONS[index]).fontSize(20).fontColor(this.isPrimaryTabActive(index)?this.brandPurple:#8E94A6)Text(NAV_ITEMS[index]).fontSize(12).fontColor(this.isPrimaryTabActive(index)?this.brandPurple:#747A92)}.onClick((){this.openPrimaryTab(index);// ← 调用父组件的方法})}而FeatureCard通过modeIndex参数和onClick回调调用openCreationFlowBuilderprivateFeatureCard(title:string,desc:string,icon:string,color:string,modeIndex:number){// ....onClick((){this.openCreationFlow(modeIndex);// 回调父组件方法})}这种模式本质上是一种事件冒泡——子 UI 片段通过回调函数通知父组件执行逻辑父组件持有状态的修改权。7. 对比总结特性PropLinkStorageLink数据流单向父→子双向父↔子全局双向使用场景子组件展示父数据父子实时同步状态跨页面/跨组件声明方式Prop variable: typeLink variable: typeStorageLink(key) variable: type传递方式直接传值variable{value}$引用variable: $stateVar通过 AppStorage 自动同步子组件修改可修改不影响父修改后同步到父修改后同步到所有使用者性能特征浅比较仅值变化时更新引用绑定实时同步AppStorage 代理跨页面无延迟项目中示例Builder参数模拟PhotoRecognitionComponentBreakpointSystemcurrentBreakpoint8. 小结本文从三个维度梳理了 ArkUI 的数据通信体系Link父子组件双向绑定用$语法传递引用适合紧密耦合的组件对。Prop父子组件单向传值适合子组件只读展示或独立维护副本。StorageLink AppStorage应用级全局状态通过键值对实现任意组件的状态共享是实现响应式布局的基石。此外我们还学习了Router getParams()的页面间传参方式以及回调函数模式在 Builder 中的反向通信用法。在实际开发中数据通信方案的选择应遵循最小化共享范围原则能用局部 State 解决的不用 Link能用 Link 解决的不用 StorageLink避免过度设计导致的状态管理混乱。下一篇预告第1.5篇《Builder 与 BuilderParam——复用 UI 的利器》。当你的页面中有大量重复的 UI 片段时Builder 是如何让代码变得简洁而优雅的我们将在下一篇中深入探讨。