ArkTS 进阶之道(25):@StorageLink/@StorageProp 应用级状态绑定边界——为啥双向绑定 vs 单向只读
ArkTS 进阶之道(25):@StorageLink/@StorageProp 应用级状态绑定边界——为啥双向绑定 vs 单向只读
本文是「ArkTS 进阶之道」系列第 25 篇,续「ArkUI 状态联动」深水区。上篇讲 LocalStorage 页面级状态存储槽边界(同页面多组件共享,页面卸载就销毁)。本文讲应用级状态绑定边界:@StorageLink 双向绑定 vs @StorageProp 单向只读——根因在双向绑定槽 vs 单向只读槽。能力系列篇 25 讲过 @StorageLink/@StorageProp 怎么用,本文讲为哈 @StorageLink 双向绑定合法 @StorageProp 单向只读组件不能改——根因在双向绑定槽 vs 单向只读槽。
一、开篇:@StorageLink 不是单向只读,是双向绑定 AppStorage 键
你写 React 时,应用级状态绑定是「useSelector 魔法」(useSelector 双向 sync redux store):
// React 应用级状态绑定:useSelector + useDispatch 双向 sync function Comp() { const count = useSelector(state => state.count) // 单向读 redux store const dispatch = useDispatch() return <button onClick={() => dispatch({ type: 'INC' })}>count={count}</button> // 改用 dispatch } // React 应用级状态绑定:useSelector 单向读 + useDispatch 改(双向 sync 魔法)你写鸿蒙 ArkTS 时,@StorageLink双向绑定 AppStorage 键——组件改互同步:
// ArkTS @StorageLink 双向绑定 AppStorage 键 AppStorage.setOrCreate('appCount', 0) @Component struct LinkComp { @StorageLink('appCount') appCount: number = 0 // ✅ @StorageLink 双向绑定(改互同步) build() { Button(`appCount=${this.appCount}`) .onClick(() => { this.appCount++ }) // ✅ 组件改 → AppStorage 同步 → 其他组件同步 } } // @StorageLink 双向绑定 AppStorage 键:组件改 → AppStorage 同步 → 其他组件同步双向useSelector 魔法 vs 双向绑定槽的区别:React 把应用级状态绑定当 useSelector 魔法(单向读 + useDispatch 改),ArkTS 把 @StorageLink 当「双向绑定槽」(组件改互同步)。根因不是 useSelector 魔法是双向绑定槽——@StorageLink 双向绑定 AppStorage 键,组件改 → AppStorage 同步 → 其他组件同步双向。
二、根因:@StorageLink/@StorageProp 的双向绑定槽 vs 单向只读槽机制
鸿蒙 ArkUI 的 @StorageLink/@StorageProp 是应用级状态绑定槽——@StorageLink 双向绑定槽,@StorageProp 单向只读槽,来自三重绑定机制。
机制 1:@StorageLink 双向绑定 AppStorage 键——组件改互同步
@StorageLink 荬饰器双向绑定 AppStorage 键——组件改 → AppStorage 同步 → 其他组件同步双向:
AppStorage.setOrCreate('appCount', 0) @Component struct LinkComp { @StorageLink('appCount') appCount: number = 0 // ✅ @StorageLink 双向绑定(改互同步) build() { Column() { Text(`appCount = ${this.appCount}`) Button('改 appCount++') .onClick(() => { this.appCount++ }) // ✅ 组件改 → AppStorage 同步 → 其他组件同步 } } } // @StorageLink 双向绑定 AppStorage 键:组件改 → AppStorage 同步 → 其他组件同步双向@StorageLink 双向绑定 AppStorage 键:@StorageLinkappCount荬饰器双向绑定 AppStorage 键——组件改appCount→ AppStorage 同步 → 其他组件同步双向。根因不是单向只读是双向绑定槽——@StorageLink 双向绑定 AppStorage 键,组件改互同步双向。
机制 2:@StorageProp 单向只读绑定——AppStorage 改组件同步,组件不能改
@StorageProp 荬饰器单向只读绑定——AppStorage 改 → 组件同步,组件改只改本地副本不回传:
AppStorage.setOrCreate('appCount', 0) @Component struct PropComp { @StorageProp('appCount') appCount: number = 0 // ⚠ @StorageProp 单向只读(组件不能改) build() { Column() { Text(`appCount = ${this.appCount}(只读,组件不能改)`) Button('试改 appCount++') .onClick(() => { // ⚠ @StorageProp 只读:改只改组件本地副本,不回传 AppStorage(单向只读) this.appCount++ // ⚠ 只改本地副本,AppStorage 不同步,其他组件不同步 }) } } } // @StorageProp 单向只读绑定:AppStorage 改 → 组件同步,组件改只改本地副本不回传@StorageProp 单向只读绑定:@StoragePropappCount荬饰器单向只读绑定——AppStorage 改appCount→ 组件同步,组件改appCount只改本地副本不回传 AppStorage。根因不是双向同步是单向只读槽——@StorageProp 单向只读绑定,AppStorage 改组件同步,组件改不回传。
机制 3:@StorageLink vs @StorageProp 边界——双向绑定 vs 单向只读
@StorageLink双向绑定(组件改互同步),@StorageProp单向只读(AppStorage 改组件同步,组件改不回传)——根因都是绑定 AppStorage 键但绑的机制不同:
AppStorage.setOrCreate('appCount', 0) // ✅ @StorageLink 路径:双向绑定(组件改 → AppStorage 同步 → 其他组件同步双向) @Component struct LinkComp { @StorageLink('appCount') appCount: number = 0 // ✅ @StorageLink 双向绑定 build() { Button(`LinkComp ${this.appCount}`) .onClick(() => { this.appCount++ }) // ✅ 组件改 → AppStorage 同步 → 其他组件同步 } } // ⚠ @StorageProp 路径:单向只读(AppStorage 改 → 组件同步,组件改不回传) @Component struct PropComp { @StorageProp('appCount') appCount: number = 0 // ⚠ @StorageProp 单向只读 build() { Button(`PropComp ${this.appCount}`) .onClick(() => { this.appCount++ }) // ⚠ 改只改本地副本,AppStorage 不同步 } } // @StorageLink vs @StorageProp 边界:双向绑定(改互同步)vs 单向只读(组件改不回传)@StorageLink vs @StorageProp 边界:@StorageLink 路径——双向绑定(组件改 → AppStorage 同步 → 其他组件同步双向,推荐组件要改用)。@StorageProp 路径——单向只读(AppStorage 改 → 组件同步,组件改只改本地副本不回传,推荐组件只读用)。根因都是绑定 AppStorage 键但绑的机制不同——@StorageLink 双向绑定槽(改互同步),@StorageProp 单向只读槽(组件改不回传)。
机制 4:AppStorage.set 改 → @StorageLink + @StorageProp 都同步
AppStorage.set改 AppStorage 键→ @StorageLink + @StorageProp 都同步(AppStorage 是源):
AppStorage.setOrCreate('appCount', 0) @Entry @Component struct Index { @StorageLink('appCount') linkCount: number = 0 // ✅ @StorageLink 双向绑定 build() { Column() { Button('直接改 AppStorage.set("appCount", 50)') .onClick(() => { AppStorage.set('appCount', 50) // ✅ AppStorage 改 → @StorageLink 同步 + @StorageProp 同步 // ✅ AppStorage 是源:@StorageLink 和 @StorageProp 都从 AppStorage 同步 }) } } } // AppStorage.set 改 → @StorageLink + @StorageProp 都同步(AppStorage 是源)AppStorage.set 改 → @StorageLink + @StorageProp 都同步:AppStorage.setappCount改 AppStorage 键 → @StorageLink + @StorageProp 都同步(AppStorage 是源)。根因不是组件改是 AppStorage 源——AppStorage.set 改 → @StorageLink + @StorageProp 都同步,AppStorage 是源。
三、真机配图:@StorageLink/@StorageProp 应用级状态绑定边界——双向 vs 只读
初始态(LinkComp appCount=0 葝色双向、PropComp appCount=0 橙色只读均绑定边界初始态齐):
点调三按钮后(LinkComp appCount=50 葝色被 AppStorage.set 直接改 50 覆盖、PropComp appCount=50 橙色只读同步、@StorageLink 双向 vs @StorageProp 只读边界证据齐):
对比证据:点 LinkComp 改 appCount++ 后 AppStorage 同步(@StorageLink 双向),点 PropComp 试改 appCount++ 后只改本地副本不回传(@StorageProp 只读),点直接改 AppStorage.set(“appCount”, 50) 后 LinkComp + PropComp 都同步 50(AppStorage 是源)。@StorageLink 双向绑定 vs @StorageProp 单向只读——@StorageLink 绑定 AppStorage 键改互同步双向,@StorageProp 绑定 AppStorage 键只读组件改不回传。
四、真解法:@StorageLink/@StorageProp 应用级状态绑定的三个场景
场景 1:@StorageLink 双向绑定计数器(90% 场景首选,组件要改用双向)
AppStorage.setOrCreate('appCount', 0) @Component struct LinkComp { @StorageLink('appCount') appCount: number = 0 // ✅ @StorageLink 双向绑定(改互同步) build() { Column() { Text(`appCount = ${this.appCount}`) Button('改 appCount++') .onClick(() => { this.appCount++ }) // ✅ 组件改 → AppStorage 同步 → 其他组件同步 } } }为哈能跑:@StorageLink 双向绑定计数器——双向绑定槽,@StorageLinkappCount双向绑定 AppStorage 键,组件改appCount→ AppStorage 同步 → 其他组件同步双向。首选这个,90% 的场景组件要改应用级状态用 @StorageLink 双向绑定槽就够。要写「组件改应用级状态(计数器等)」时用这个——不用 @StorageProp 单向只读(组件改不回传),@StorageLink 双向绑定改互同步。
场景 2:@StorageProp 单向只读绑定展示(组件只读用单向,AppStorage 改组件同步)
AppStorage.setOrCreate('appCount', 0) @Component struct PropComp { @StorageProp('appCount') appCount: number = 0 // ⚠ @StorageProp 单向只读(组件不能改) build() { Text(`appCount = ${this.appCount}(只读展示,组件不能改)`) // ✅ AppStorage 改 → 组件同步只读展示 } } // @StorageProp 单向只读绑定展示:AppStorage 改 → 组件同步只读展示,组件不能改为哈能跑:@StorageProp 单向只读绑定展示——单向只读槽,@StoragePropappCount单向只读绑定 AppStorage 键,AppStorage 改appCount→ 组件同步只读展示,组件不能改。要写「组件只读展示应用级状态(只读展示等)」时用这个——不用 @StorageLink 双向绑定(组件改会回传),@StorageProp 单向只读组件不能改。
场景 3:@StorageLink + @StorageProp 混用(改组件用 @StorageLink,只读组件用 @StorageProp)
AppStorage.setOrCreate('appCount', 0) // ✅ 改组件用 @StorageLink 双向绑定(组件改 → AppStorage 同步) @Component struct EditComp { @StorageLink('appCount') appCount: number = 0 // ✅ @StorageLink 双向绑定(改互同步) build() { Column() { TextInput({ text: `${this.appCount}`, placeholder: '输入值' }) .onChange((value: string) => { this.appCount = parseInt(value) || 0 }) // ✅ 改 → AppStorage 同步 } } } // ✅ 只读组件用 @StorageProp 单向只读(AppStorage 改 → 组件同步只读展示) @Component struct ShowComp { @StorageProp('appCount') appCount: number = 0 // ✅ @StorageProp 单向只读(组件不能改) build() { Text(`只读展示 appCount = ${this.appCount}`) // ✅ AppStorage 改 → 组件同步只读展示 } } // @StorageLink + @StorageProp 混用:改组件用 @StorageLink 双向,只读组件用 @StorageProp 单向只读为哈能跑:@StorageLink + @StorageProp 混用——改组件用 @StorageLink 双向绑定(组件改 → AppStorage 同步),只读组件用 @StorageProp 单向只读(AppStorage 改 → 组件同步只读展示)。要写「改组件 + 只读组件混用(编辑 + 展示等)」时用这个——改组件用 @StorageLink 双向,只读组件用 @StorageProp 单向只读。
五、一句话哲学
@StorageLink 不是单向只读,是双向绑定 AppStorage 键的改互同步;@StorageProp 是单向只读的组件不能改。ArkUI 的 @StorageLink 荬饰器双向绑定 AppStorage 键,组件改互同步双向。根因不是单向只读是双向绑定槽——@StorageLink 双向绑定 AppStorage 键(组件改 → AppStorage 同步 → 其他组件同步双向)+ @StorageProp 单向只读绑定(AppStorage 改 → 组件同步,组件改只改本地副本不回传)+ @StorageLink vs @StorageProp 边界(双向绑定 vs 单向只读)+ AppStorage.set 改 → @StorageLink + @StorageProp 都同步(AppStorage 是源)。对比 React 应用级状态绑定 useSelector 魔法(单向读 + useDispatch 改),ArkTS @StorageLink 双向绑定槽改互同步。
状态联动深水区串讲:@Watch 绑 @State 变触发副作用回调槽(篇 68)+ @Link $ 语法双向绑定跨组件同步(篇 69)+ @Provide/@Consume 跨层级传递槽祖辈后辈同步(篇 70)+ @Observed/@ObjectLink 嵌套对象观测槽子属性改刷 UI(篇 71)+ AppStorage 应用级状态存储槽所有页面共享(篇 72)+ LocalStorage 页面级状态存储槽同页面多组件共享(篇 73)+ @StorageLink/@StorageProp 应用级状态绑定边界双向 vs 只读(篇 74,@StorageLink 双向绑定 vs @StorageProp 单向只读)——续「ArkUI 状态联动」深水区,讲状态联动深水区(@Watch 寕作用回调槽边界 + @Link $ 语法双向绑定边界 + @Provide/@Consume 跨层级传递槽边界 + @Observed/@ObjectLink 嵌套对象观测槽边界 + AppStorage 应用级状态存储槽边界 + LocalStorage 页面级状态存储槽边界 + @StorageLink/@StorageProp 应用级状态绑定边界)。
系列预告:下篇(篇 75)讲 @LocalStorageLink/@LocalStorageProp 页面级状态绑定边界(LocalStorage 绑定根因),续「ArkUI 状态联动」深水区。五阶段哲学体系:类型哲学(50-52)→ 作用域哲学(53-55)→ 状态哲学(56-59)→ 渎染哲学(60-62)→ 组件设计(63-67)→ 状态联动深水区(68+)讲清 ArkTS/ArkUI 进阶哲学。
能力系列回链
| 能力系列篇 | 本文进阶点 |
|---|---|
| 篇 25 @StorageLink/@StorageProp 用法 | @StorageLink/@StorageProp 应用级状态绑定边界根因(双向绑定 vs 单向只读) |
| 篇 23 AppStorage 用法 | 应用级状态存储槽边界(所有页面共享页面卸载状态仍存在) |
| 篇 59 @Watch 用法 | 状态联动深水区:@Watch 儝作用回调槽 vs onClick 直接调副作用边界 |
真机 demo 完整代码
// 篇 74 demo:@StorageLink/@StorageProp 应用级状态绑定边界 // @StorageLink 双向绑定 AppStorage 键(改互同步)vs @StorageProp 单向只读绑定(组件不能改) AppStorage.setOrCreate('appCount', 0) AppStorage.setOrCreate('appTheme', '#007DFF') // ✅ @StorageLink 双向绑定组件:改互同步(组件改 → AppStorage 同步,AppStorage 改 → 组件同步) @Component struct LinkComp { @StorageLink('appCount') appCount: number = 0 // ✅ @StorageLink 双向绑定(改互同步) build() { Column({ space: 6 }) { Text('组件 LinkComp(@StorageLink 双向绑定)') .fontSize(11).fontColor('#2563eb').fontWeight(FontWeight.Bold) Text(`appCount = ${this.appCount}`).fontSize(13).fontColor('#2563eb') Button('LinkComp 改 appCount++(@StorageLink 双向:组件改 → AppStorage 同步)') .width('92%').height(36).fontSize(10) .onClick(() => { this.appCount++ }) // ✅ 组件改 → AppStorage 同步 → 其他组件同步 } .width('92%').padding(8).backgroundColor('#e0f0ff').borderRadius(6) } } // ⚠ @StorageProp 单向只读绑定组件:AppStorage 改 → 组件同步,组件不能改(只读) @Component struct PropComp { @StorageProp('appCount') appCount: number = 0 // ⚠ @StorageProp 单向只读(组件不能改) build() { Column({ space: 6 }) { Text('组件 PropComp(@StorageProp 单向只读)') .fontSize(11).fontColor('#ff6600').fontWeight(FontWeight.Bold) Text(`appCount = ${this.appCount}(只读,组件不能改)`).fontSize(13).fontColor('#ff6600') Button('PropComp 试改 appCount++(@StorageProp 只读,改不回传 AppStorage)') .width('92%').height(36).fontSize(10) .onClick(() => { // ⚠ @StorageProp 只读:改只改组件本地副本,不回传 AppStorage(单向只读) this.appCount++ // ⚠ 只改本地副本,AppStorage 不同步,其他组件不同步 }) } .width('92%').padding(8).backgroundColor('#fff0e0').borderRadius(6) } } @Entry @Component struct Index { @StorageLink('appTheme') appTheme: string = '#007DFF' @State log: string = '(未操作)' build() { Column({ space: 12 }) { Text('篇 74 配图:@StorageLink/@StorageProp 应用级状态绑定边界') .fontSize(16).fontWeight(FontWeight.Bold).margin({ top: 16, bottom: 4 }) Text('@StorageLink 双向绑定(改互同步)vs @StorageProp 单向只读(组件不能改)') .fontSize(10).fontColor('#888').margin({ bottom: 8 }) LinkComp() PropComp() // ✅ 直接改 AppStorage(@StorageLink 同步,@StorageProp 也同步) Button('直接改 AppStorage.set("appCount", 50)') .width('92%').height(40).fontSize(12) .onClick(() => { AppStorage.set('appCount', 50) // ✅ AppStorage 改 → @StorageLink 同步 + @StorageProp 同步 this.log = 'AppStorage.set appCount=50(@StorageLink + @StorageProp 都同步)' }) Text(`日志:${this.log}`).fontSize(10).fontColor('#333').margin({ top: 4 }) } .width('100%').height('100%').alignItems(HorizontalAlign.Center) } }写鸿蒙 ArkUI 记住:@StorageLink 不是单向只读是双向绑定 AppStorage 键的改互同步——@StorageLink 荬饰器双向绑定 AppStorage 键,组件改互同步双向。根因不是单向只读是双向绑定槽——@StorageLink 双向绑定 AppStorage 键(组件改 → AppStorage 同步 → 其他组件同步双向)+ @StorageProp 单向只读绑定(AppStorage 改 → 组件同步,组件改只改本地副本不回传)+ @StorageLink vs @StorageProp 边界(双向绑定 vs 单向只读)+ AppStorage.set 改 → @StorageLink + @StorageProp 都同步(AppStorage 是源)。@StorageLink 双向绑定计数器用双向绑定槽(首选,90% 场景),@StorageProp 单向只读绑定展示用单向只读槽组件不能改,@StorageLink + @StorageProp 混用改组件用 @StorageLink 双向只读组件用 @StorageProp 单向只读。双向绑定 vs 单向只读是 ArkUI 状态联动深水区核心!
