HarmonyOS7 @Prop 和 @Link 到底怎么选:先问状态归谁

📅 2026/7/21 8:13:38
HarmonyOS7 @Prop 和 @Link 到底怎么选:先问状态归谁
文章目录前言为什么这个问题经常被写乱一张表先分清边界先把页面目标想清楚完整 ArkUI 示例把关键代码一段段拆开什么时候别用 Link新手最容易踩的坑放进真实项目还要补什么写在最后前言Prop和Link很多人会背概念但真正写页面时还是容易乱用。简单说Prop更像“父组件给子组件看一眼”子组件不负责改它Link更像“父子组件共同维护同一份状态”子组件可以反向影响父组件。选择Prop还是Link不要先背 API先问这份状态到底归谁。这篇用筛选栏做例子。筛选项配置是父页面提供的当前选中值需要子组件点击后改回父页面这正好能把两者区别讲清楚。为什么这个问题经常被写乱Prop 和 Link 到底怎么选 这类内容很容易被写成“代码能跑就算讲完了”但对初学者来说这恰恰是最不够的地方。真正让人卡住的往往不是某个组件名记不住而是不知道这段代码为什么要这样拆、状态为什么要这样放、以后需求变化时应该从哪里改。所以这篇文章不只想给你一个能跑的例子更想把背后的判断过程讲清楚。你只要把这个判断过程吃透后面自己改页面、补需求、查问题时心里会稳很多。一张表先分清边界装饰器适合场景我会避免Prop标题、数量、配置项、只读展示子组件内部直接修改Link表单值、开关、当前选中项静态文案、普通配置数组如果一个字段只是拿来展示别给它双向修改能力。组件权限越小后期越安全。能用Prop表达的不要为了省事改成Link。双向绑定用多了状态来源会变得很难追。先把页面目标想清楚在真正写代码之前先别急着盯着 API。更有用的做法是先想清楚这个页面到底想解决什么问题用户最在意的反馈是什么哪些状态必须一直保持一致。当你先把这条主线想明白再回头看组件和状态设计很多选择都会顺理成章。对小白来说这一步尤其重要因为它能帮你从“照着抄”慢慢过渡到“看得懂、改得动”。完整 ArkUI 示例下面这个例子有三个组件单个筛选芯片、筛选栏、页面本身。option用PropselectedId用Link。interfaceFilterOption{id:stringlabel:stringcount:number}Componentstruct FilterChip{Propoption:FilterOptionLinkselectedId:stringbuild(){Row({space:4}){Text(this.option.label).fontSize(14)Text(${this.option.count}).fontSize(11).fontColor(this.selectedIdthis.option.id?Color.White:#666666)}.padding({left:12,right:12,top:7,bottom:7}).backgroundColor(this.selectedIdthis.option.id?#1E88E5:#F0F2F5).borderRadius(16).onClick((){this.selectedIdthis.option.id})}}Componentstruct FilterBar{Propoptions:FilterOption[]LinkselectedId:stringbuild(){Row({space:8}){ForEach(this.options,(item:FilterOption){FilterChip({option:item,selectedId:this.selectedId})},(item:FilterOption)item.id)}}}EntryComponentstruct FilterDemoPage{StateselectedId:stringallprivateoptions:FilterOption[][{id:all,label:全部,count:28},{id:todo,label:待处理,count:6},{id:done,label:已完成,count:22}]privatecurrentLabel():string{constfound:FilterOption|undefinedthis.options.find((item:FilterOption)item.idthis.selectedId)returnfound?found.label:全部}build(){Column({space:14}){FilterBar({options:this.options,selectedId:this.selectedId})Text(当前筛选${this.currentLabel()}).fontSize(16).width(100%)Text(这里可以接列表查询切换筛选后只需要根据 selectedId 请求对应数据。).fontSize(13).fontColor(#666666)}.padding(16)}}把关键代码一段段拆开FilterChip.option用Prop因为芯片只需要展示它的标题和数量。它不应该修改option.count也不应该改配置本身。FilterChip.selectedId用Link因为点击芯片后要把父页面的当前筛选值改掉。这个动作是子组件触发的但状态仍属于父页面。FilterBar.options也用Prop。筛选栏只是消费配置不应该拿到修改配置数组的权力。真实项目里配置一般来自接口、枚举或父页面状态子组件改它会让边界变模糊。currentLabel()没有再额外存一个selectedLabel而是从selectedId和options里算出来。能算出来的值尽量不要重复存否则两份状态迟早会不同步。什么时候别用 Link如果子组件只是显示数量、标题、头像、状态文案用Prop就够了。给这些字段Link相当于允许子组件反向改父级数据权限太大。如果你只是想让子组件通知父组件发生了某件事也不一定非要Link。有时候传一个回调更清楚比如onSubmit、onDelete、onSelect。Link更适合“这个值本身就是父子共同编辑”的场景。新手最容易踩的坑这一类示例最容易让人产生错觉界面出来了就以为已经掌握了。其实真正容易出问题的地方通常都在效果之外比如状态有没有收拢、失败后怎么兜底、以后要扩展时会不会牵一发动全身。所以你练这篇内容时别只看“现在能不能跑”还要继续看“以后好不好改”。能把这个习惯养起来你写出来的页面会比单纯照着示例拼出来的页面稳很多。放进真实项目还要补什么示例代码的重点是把核心思路讲明白所以很多工程化细节会故意省掉。真正落到项目里时你通常还要继续补接口联动、异常处理、边界保护、资源抽离以及和其他页面状态之间的配合。写在最后比较稳的做法是分三步走先把结构和职责立住再把真实业务接进去最后再优化视觉和交互体验。这样改出来的页面不只是“能演示”而是真的更接近可以长期维护的业务代码。