ArkTS 进阶之道(8):@Prop/@Link 父子传值——单向 vs 双向数据流根因

ArkTS 进阶之道(8):@Prop/@Link 父子传值——单向 vs 双向数据流根因 ArkTS 进阶之道8Prop/Link 父子传值——单向 vs 双向数据流根因本文是「ArkTS 进阶之道」系列第 8 篇续「ArkUI 状态哲学」阶段。上篇讲 State 装饰器赋值就刷 UI 的依赖追踪机制篇 56——单组件内部状态。本文讲父子组件传值Prop 单向 vs Link 双向——根因在数据流方向绑定机制Prop 绑单向「父传子」Link 绑双向「父子同步」。能力系列篇 14 讲过 Prop/Link 怎么用本文讲为哈单向 vs 双向——根因在数据流绑定。一、开篇Prop/Link 不是轻量的语法糖是数据流方向的显式绑定你写 TypeScript/React 时父子传值是「魔法」React props 单向传子子改要调父 setter// React props 单向 setter 回调 function Child({ value, onChange }: { value: number; onChange: (v: number) void }) { return Button onClick{() onChange(value 1)}子改 {value}/Button } function Parent() { const [count, setCount] useState(0) return Child value{count} onChange{setCount} / ← 父传 value 单向子改调 onChange 回父改 } // 子改 value 不刷单向子调 onChange 才回父改刷手动双向你写鸿蒙 ArkTS 时Prop/Link 装饰器显式绑数据流方向——不用手写 setter 回调// ArkTS Prop 单向 Link 双向显式绑定 Component struct PropChild { Prop value: number // Prop 单向父传子子改不影响父 build() { Text(${this.value}) } } Component struct LinkChild { Link synced: number // Link 双向父子同步变 build() { Text(${this.synced}) } } Entry Component struct Index { State propValue: number 1 State linkValue: number 1 build() { Column() { PropChild({ value: this.propValue }) // 父传 Prop 单向 LinkChild({ synced: this.linkValue }) // 父传 Link 双向 } } }魔法 vs 绑定的区别React 把父子传值当「魔法」你手写 setter 回调实现双向ArkTS 把 Prop/Link 当「数据流方向绑定」装饰器编译期绑方向不用手写 setter。根因不是魔法是绑定——Prop 编译期绑单向「父变子变子变父不变」Link 编译期绑双向「父子互变同步」。二、根因Prop/Link 的数据流方向绑定机制鸿蒙 ArkUI 的 Prop/Link 是数据流方向绑定——编译期给字段加方向追踪代码父变触发子刷 / 子改是否回父来自三重绑定机制。机制 1Prop 单向绑定——父变子变子变父不变Prop 装饰器编译期绑单向数据流——父 State 变触发子 Prop 刷子 Prop 改不回父Component struct PropChild { Prop value: number // Prop 单向父变子变子变父不变 build() { Column() { Text(子 Prop ${this.value}) ← 依赖 Prop value Button(子改 Prop) .onClick(() { this.value this.value 100 // 子改 Prop只刷自己 UI父不变 }) } } } Entry Component struct Index { State propValue: number 1 // 父管 Prop 的源 build() { Column() { Text(父 State ${this.propValue}) PropChild({ value: this.propValue }) // 父传 Prop 单向 Button(父改 State) .onClick(() { this.propValue // 父改 State单向传 Prop 子刷新 }) } } }单向绑定Propvalue编译期绑单向——父propValue变触发子value刷父变子变子value改不回父propValue子变父不变。赋值this.value this.value 100只刷子 UI父propValue仍原值。单向数据流不用手写 setter装饰器绑方向。机制 2Link 双向绑定——父子互变同步Link 装饰器编译期绑双向数据流——父 State 变触发子 Link 刷子 Link 改也回父 StateComponent struct LinkChild { Link synced: number // Link 双向父子互变同步 build() { Column() { Text(子 Link ${this.synced}) ← 依赖 Link synced Button(子改 Link) .onClick(() { this.synced this.synced 100 // 子改 Link父子同步变双向 }) } } } Entry Component struct Index { State linkValue: number 1 // 父管 Link 的源 build() { Column() { Text(父 State ${this.linkValue}) LinkChild({ synced: this.linkValue }) // 父传 Link 双向 Button(父改 State) .onClick(() { this.linkValue // 父改 State双向同步 Link 子也变 }) } } }双向绑定Linksynced编译期绑双向——父linkValue变触发子synced刷父变子变子synced改也回父linkValue子变父也变。赋值this.synced this.synced 100父子同步变父子 UI 均刷。双向数据流不用手写 setter 回调装饰器绑方向。机制 3Prop/Link 追踪范围不同——单向只追父变 / 双向追父子互变Prop/Link 编译期绑的追踪范围不同——Prop 只追父变单向通知子Link 追父子互变双向同步// Prop 追踪范围只追父变单向通知子 // 父 propValue 变 → 通知子 value 刷父变子变 // 子 value 改 → 不通知父子变父不变单向只追父变方向 // Link 追踪范围追父子互变双向同步 // 父 linkValue 变 → 通知子 synced 刷父变子变 // 子 synced 改 → 通知父 linkValue 变子变父也变双向追互变方向追踪范围区别Prop 编译期只追「父变单向通知子」一个方向Link 编译期追「父变通知子 子变通知父」两个方向。根因不是单向双向的语法区别是编译期绑的追踪范围不同——Prop 绑一个方向Link 绑两个方向。三、真机配图Prop 单向 vs Link 双向 数据流对比证据初始态父 propValue1、父 linkValue1、子 Prop1、子 Link1 均初始值点调子改 Prop 子改 Link 父改 State 后父 propValue1 不变子 Prop 变 101 单向、父 linkValue2 子 Link 同步变 102 双向均对比证据齐对比证据子改 Prop 后子变 101 父 propValue 仍 1单向子变父不变子改 Link 后父子同步变双向互变同步父改 State propValue 单向传子刷新父改 State linkValue 双向同步子也变。Prop 单向 vs Link 双向不是语法区别是编译期绑的数据流追踪范围不同——Prop 绑一个方向Link 绑两个方向。四、真解法Prop/Link 用法的三个场景场景 1Prop 单向传值子只读不改父90% 场景首选Component struct DisplayChild { Prop title: string // Prop 单向子只读显示不改父 Prop count: number build() { Column() { Text(this.title) ← 依赖 Prop title 只读显示 Text(count ${this.count}) ← 依赖 Prop count 只读显示 } } } Entry Component struct Index { State title: string 标题 State count: number 0 build() { Column() { DisplayChild({ title: this.title, count: this.count }) // 父传 Prop 单向 Button(父改) .onClick(() { this.count // 父改 State 单向传子刷新 }) } } }为哈能跑Prop 单向绑「父变子变子变父不变」子只读显示不改父。首选这个90% 的场景子只读显示用 Prop 单向就够。要写「子显示父数据不改」时用这个——单向数据流清晰子改不影响父状态边界。场景 2Link 双向同步父子互改同步要双向协作时Component struct EditChild { Link synced: number // Link 双向子可改父也同步变 build() { Column() { Text(同步 ${this.synced}) Button(子改) .onClick(() { this.synced // 子改 Link 父也同步变双向 }) } } } Entry Component struct Index { State shared: number 0 // 父管 Link 的源 build() { Column() { Text(父 ${this.shared}) EditChild({ synced: this.shared }) // 父传 Link 双向 Button(父改) .onClick(() { this.shared // 父改 State 双向同步子也变 }) } } }为哈能跑Link 双向绑「父子互变同步」子可改父也同步变。要写「父子双向协作共享状态」时用这个——双向数据流不用手写 setter 回调装饰器绑两个方向追踪。注意 Link 的父端必须是 State双向同步要父管源状态。场景 3Prop Link 混用单向显示 双向编辑分离Component struct MixedChild { Prop readonlyTitle: string // Prop 单向只读显示标题 Link editableValue: number // Link 双向可编辑值同步 build() { Column() { Text(this.readonlyTitle) ← Prop 只读显示 Text(值 ${this.editableValue}) ← Link 可编辑同步 Button(子改值) .onClick(() { this.editableValue ← 子改 Link 父也变双向 // this.readonlyTitle 新 ← 子改 Prop 不影响父单向不这么用 }) } } } Entry Component struct Index { State title: string 标题 State value: number 0 build() { Column() { MixedChild({ readonlyTitle: this.title, editableValue: this.value }) Button(父改标题) .onClick(() { this.title 标题${this.value} }) ← 父改 Prop 单向传子 } } }为哈能跑Prop Link 混用——Prop 管只读显示字段单向父传子Link 管可编辑字段双向父子同步。要写「子组件有只读字段 可编辑字段分离」时用这个——单向双向按字段职责分离数据流边界清晰。五、一句话哲学Prop/Link 不是语法区别是编译期绑的数据流方向追踪范围不同。ArkUI 的 Prop 单向绑「父变子变子变父不变」一个方向追踪Link 双向绑「父子互变同步」两个方向追踪。根因不是单向双向的语法区别是编译期绑的追踪范围——Prop 绑一个方向父变通知子Link 绑两个方向父变通知子 子变通知父。不用手写 setter 回调装饰器绑方向。状态哲学阶段串讲State 单组件内部篇 56赋值就刷 UI 依赖追踪→ Prop/Link 父子传值篇 57单向 vs 双向数据流绑定——从「单组件内部状态」到「父子组件传值」扩展根因都是编译期绑追踪范围。下一篇ArkTS 进阶之道9—— Provide/Consume 跨层传值为啥不叫全局变量对应能力系列篇 15讲根因——续「ArkUI 状态哲学」阶段。能力系列回链能力系列篇本文进阶点篇 14 Prop/Link 用法Prop/Link 数据流方向绑定根因篇 13 State 基础用法上一篇State 依赖追踪机制篇 15 Provide/Consume 用法下篇预告跨层传值为啥不叫全局变量真机 demo 完整代码// 篇 57 demoProp 单向 vs Link 双向 数据流对比 // 显式 interface 声明见篇 51 装对象字量约束 interface ICounterObj { count: number } // ✅ Prop 单向父传子子改不影响父 Component struct PropChild { Prop value: number // Prop 单向接父值子改不影响父 build() { Column({ space: 8 }) { Text(Prop 子接收 ${this.value}).fontSize(14).fontColor(#2563eb) Button(子改 Prop只改自己不影响父) .width(92%).height(40).fontSize(12) .onClick(() { this.value this.value 100 // 子改 Prop 只刷自己 UI父不变 }) } .alignItems(HorizontalAlign.Center) } } // ✅ Link 双向父子互改同步 Component struct LinkChild { Link synced: number // Link 双向同步子改父也变 build() { Column({ space: 8 }) { Text(Link 子同步 ${this.synced}).fontSize(14).fontColor(#dc2626) Button(子改 Link父子同步变) .width(92%).height(40).fontSize(12) .onClick(() { this.synced this.synced 100 // 子改 Link 父也变双向同步 }) } .alignItems(HorizontalAlign.Center) } } Entry Component struct Index { // 父组件 State State propValue: number 1 // 父管 Prop 的源 State linkValue: number 1 // 父管 Link 的源 State log: string (未操作) build() { Column({ space: 12 }) { Text(篇 57 配图Prop 单向 vs Link 双向 数据流) .fontSize(18).fontWeight(FontWeight.Bold).margin({ top: 20, bottom: 8 }) Text(Prop 子改不影响父 vs Link 父子同步变对比证据) .fontSize(12).fontColor(#888).margin({ bottom: 16 }) Column({ space: 6 }) { Text(父 State propValue ${this.propValue}).fontSize(15).fontWeight(FontWeight.Bold) Text(父 State linkValue ${this.linkValue}).fontSize(15).fontWeight(FontWeight.Bold) Text(日志${this.log}).fontSize(12).fontColor(#333).margin({ top: 4 }) } .width(92%).padding(12).backgroundColor(#f5f5f5).borderRadius(8) // 父传子Prop 单向 Link 双向 PropChild({ value: this.propValue }) LinkChild({ synced: this.linkValue }) Button(父改 State propValue单向传子) .width(92%).height(44).fontSize(14) .onClick(() { this.propValue // 父改 State单向传 Prop 子刷新 this.log 父改 propValue${this.propValue}单向传子刷子改不影响父 }) Button(父改 State linkValue双向同步子) .width(92%).height(44).fontSize(14) .onClick(() { this.linkValue // 父改 State双向同步 Link 子也变 this.log 父改 linkValue${this.linkValue}双向同步子也变 }) } .width(100%).height(100%).alignItems(HorizontalAlign.Center) } }写鸿蒙 ArkUI 记住Prop 单向绑「父变子变子变父不变」一个方向追踪Link 双向绑「父子互变同步」两个方向追踪。根因不是单向双向语法区别是编译期绑的数据流追踪范围——Prop 绑一个方向父变通知子Link 绑两个方向父变通知子 子变通知父。Prop 单向传值子只读不改父首选Link 双向同步父子互改共享状态要双向协作Prop Link 混用按字段职责分离单向双向。不用手写 setter 回调装饰器绑方向是 ArkUI 状态哲学核心