文章目录
- 前言
- 为什么状态管理这么头疼
- 三种装饰器对比
- 场景一:父子单向传递(@Prop)
- 场景二:父子双向同步(@Link)
- 场景三:跨层级传递(@Provide + @Consume)
- 场景四:列表中的状态管理
- 选型决策树
- 写在最后
前言
刚学 ArkUI 的时候,状态管理那几个装饰器把我搞懵了——@State、@Prop、@Link,长得像亲兄弟,用起来完全不一样。照着官方文档抄了一遍代码,数据就是不同步,debug 了两个小时才发现用错了装饰器。
选错装饰器,轻则数据不同步,重则整个组件不刷新。
今天把这三个装饰器掰碎了讲,看完你就知道每个场景该用谁。
为什么状态管理这么头疼
ArkUI 是声明式框架,UI 是状态的函数:状态变了,UI 自动更新。但问题来了——
- 父组件的数据,子组件能不能改?
- 改了之后父组件知不知道?
- 孙组件要访问祖先的数据怎么办?
这些问题的答案,就藏在装饰器的选择里。选对了,数据流通顺;选错了,刷新丢失、数据不同步,满屏 bug。
三种装饰器对比
先把最核心的区别摆出来:
| 维度 | @State | @Prop | @Link |
|---|---|---|---|
| 数据流向 | 组件内部,自给自足 | 父 → 子(单向) | 父 ↔ 子(双向) |
| 能否修改 | 随便改 | 能改,但不通知父组件 | 能改,同步通知父组件 |
| 类型限制 | 支持所有类型 | 只支持简单类型(string/number/boolean) | 只支持简单类型 |
| 父组件传参 | 不需要 | 子组件({ prop: this.xxx }) | 子组件({ link: $xxx }) |
|性能影响| 低 | 低 |稍高(双向绑定有监听开销) |
|使用场景| 组件私有状态 | 展示型子组件 | 表单、开关等需回传的子组件 |
划重点:@Prop 改了不通知爹,@Link 改了会通知爹。就这一句话的区别。
场景一:父子单向传递(@Prop)
父组件有个用户名,子组件只负责展示,不需要回传。
@Componentstruct ParentA{@Stateusername:string='鸿蒙开发者'build(){Column(){Text('父组件:'+this.username).fontSize(20)ChildA({name:this.username})}}}@Componentstruct ChildA{@Propname:stringbuild(){Text('子组件展示:'+this.name).fontSize(16).fontColor('#666')}}关键代码讲解:
@Prop name: string—— 子组件用 @Prop 接收,类型必须和父组件传的一致ChildA({ name: this.username })—— 父组件通过构造参数传递,注意不是$语法- 父组件修改
username,子组件会跟着更新;但子组件改name,父组件的username不变
这是"只读展示"场景的标准用法。子组件像是拿了一份复印件,随便画,原件不受影响。
场景二:父子双向同步(@Link)
子组件是个开关,用户拨动开关后,父组件的状态也要同步更新。
@Componentstruct ParentB{@StateisDarkMode:boolean=falsebuild(){Column(){Text('当前模式:'+(this.isDarkMode?'深色':'浅色')).fontSize(20)ToggleChild({mode:$isDarkMode})}}}@Componentstruct ToggleChild{@Linkmode:booleanbuild(){Toggle({type:ToggleType.Switch,isOn:this.mode}).onChange((value:boolean)=>{this.mode=value})}}关键代码讲解:
@Link mode: boolean—— 子组件用 @Link 声明,建立双向绑定ToggleChild({ mode: $isDarkMode })——注意$符号,这是 @Link 传参的固定语法this.mode = value—— 子组件修改 @Link 变量,父组件的isDarkMode同步更新
$语法就是告诉框架:这不是传值,是传引用,两边绑定了。
场景三:跨层级传递(@Provide + @Consume)
爷组件要给孙组件传数据,中间隔了一层,用 @Prop/@Link 得一层层透传,太恶心了。HarmonyOS7 提供了@Provide和@Consume解决这个问题。
@Componentstruct Grandparent{@Providetheme:string='blue'build(){Column(){Text('爷爷的主题色:'+this.theme)MiddleLayer()}}}@Componentstruct MiddleLayer{build(){Column(){Text('中间层,我不关心主题')Grandchild()}}}@Componentstruct Grandchild{@Consumetheme:stringbuild(){Text('孙子收到的主题色:'+this.theme).fontColor(this.theme==='blue'?'#1890ff':'#52c41a')}}关键代码讲解:
@Provide theme—— 祖先组件提供数据,自动向下广播@Consume theme—— 后代组件消费数据,按变量名匹配,不需要中间层转发- 中间层
MiddleLayer完全不需要知道 theme 的存在
这招在大型项目中特别好用。全局主题、用户信息这类"到处都要用"的数据,用 @Provide/@Consume 最省心。
场景四:列表中的状态管理
列表里每个 item 都有独立状态(比如选中态),这是最容易出 bug 的地方。
@Componentstruct ListDemo{@Stateitems:SelectItem[]=[{id:1,name:'ArkTS',selected:false},{id:2,name:'ArkUI',selected:false},{id:3,name:'Flex',selected:true}]build(){List(){ForEach(this.items,(item:SelectItem)=>{ListItem(){ItemRow({name:item.name,selected:item.selected,onToggle:()=>{item.selected=!item.selected}})}},(item:SelectItem)=>item.id.toString())}}}@Componentstruct ItemRow{@Propname:string@Propselected:booleanonToggle:()=>void=()=>{}build(){Row(){Text(this.name)Checkbox().select(this.selected).onChange(()=>this.onToggle())}}}关键代码讲解:
- 列表项状态通过
@Prop传入,单项选中状态用 @Prop 就够了 - 回调函数
onToggle在父组件中修改@State items,触发列表刷新 - ForEach 的第三个参数
(item) => item.id.toString()是键值生成器,别漏了,否则列表更新会出问题
选型决策树
一张图帮你快速决策:
需要状态管理? │ ├─ 组件自己用,不需要传给子组件? │ └─ ✅ @State │ ├─ 传给子组件,子组件只展示不改? │ └─ ✅ @Prop │ ├─ 传给子组件,子组件改了要同步回来? │ └─ ✅ @Link │ ├─ 跨多层组件传递? │ └─ ✅ @Provide + @Consume │ └─ 列表中的单项状态? └─ ✅ @State(父) + @Prop(子) + 回调写在最后
状态管理就一句话:数据谁拥有,修改权就归谁。子组件要改父组件的数据,要么用 @Link 双向绑定,要么用回调函数通知父组件自己改。别偷懒直接改 @Prop,改了也白改。
记住这个原则,状态管理的 90% 的坑都能避开。