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

本文是「ArkTS 进阶之道」系列第 8 篇,续「ArkUI 状态哲学」阶段。上篇讲 @State 装饰器赋值就刷 UI 的依赖追踪机制(篇 56)------单组件内部状态。本文讲父子组件传值:@Prop 单向 vs @Link 双向 ------根因在数据流方向绑定机制,@Prop 绑单向「父传子」@Link 绑双向「父子同步」。能力系列篇 14 讲过 @Prop/@Link 怎么用,本文讲为哈单向 vs 双向------根因在数据流绑定。

你写 TypeScript/React 时,父子传值是「魔法」(React props 单向传子,子改要调父 setter):

typescript 复制代码
// 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 回调:

typescript 复制代码
// 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 编译期绑双向「父子互变同步」。

鸿蒙 ArkUI 的 @Prop/@Link 是数据流方向绑定------编译期给字段加方向追踪代码,父变触发子刷 / 子改是否回父,来自三重绑定机制。

机制 1:@Prop 单向绑定------父变子变,子变父不变

@Prop 装饰器编译期绑单向数据流------父 @State 变触发子 @Prop 刷,子 @Prop 改不回父:

typescript 复制代码
@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 子刷新
        })
    }
  }
}

单向绑定 :@Prop value 编译期绑单向------父 propValue 变触发子 value 刷(父变子变),子 value 改不回父 propValue(子变父不变)。赋值 this.value = this.value + 100 只刷子 UI,父 propValue 仍原值。单向数据流不用手写 setter,装饰器绑方向。

@Link 装饰器编译期绑双向数据流------父 @State 变触发子 @Link 刷,子 @Link 改也回父 @State:

typescript 复制代码
@Component
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 子也变
        })
    }
  }
}

双向绑定 :@Link synced 编译期绑双向------父 linkValue 变触发子 synced 刷(父变子变),子 synced 改也回父 linkValue(子变父也变)。赋值 this.synced = this.synced + 100 父子同步变,父子 UI 均刷。双向数据流不用手写 setter 回调,装饰器绑方向。

@Prop/@Link 编译期绑的追踪范围不同------@Prop 只追父变单向通知子,@Link 追父子互变双向同步:

typescript 复制代码
// @Prop 追踪范围:只追父变单向通知子
// 父 propValue 变 → 通知子 value 刷(父变子变)
// 子 value 改 → 不通知父(子变父不变,单向只追父变方向)

// @Link 追踪范围:追父子互变双向同步
// 父 linkValue 变 → 通知子 synced 刷(父变子变)
// 子 synced 改 → 通知父 linkValue 变(子变父也变,双向追互变方向)

追踪范围区别:@Prop 编译期只追「父变单向通知子」一个方向,@Link 编译期追「父变通知子 + 子变通知父」两个方向。根因不是单向双向的语法区别,是编译期绑的追踪范围不同------@Prop 绑一个方向,@Link 绑两个方向。

初始态(父 propValue=1、父 linkValue=1、子 @Prop=1、子 @Link=1 均初始值):

点调子改 @Prop + 子改 @Link + 父改 @State 后(父 propValue=1 不变子 @Prop 变 101 单向、父 linkValue=2 子 @Link 同步变 102 双向均对比证据齐):

对比证据:子改 @Prop 后子变 101 父 propValue 仍 1(单向子变父不变),子改 @Link 后父子同步变(双向互变同步),父改 @State propValue 单向传子刷新,父改 @State linkValue 双向同步子也变。@Prop 单向 vs @Link 双向不是语法区别是编译期绑的数据流追踪范围不同------@Prop 绑一个方向,@Link 绑两个方向。

场景 1:@Prop 单向传值(子只读不改父,90% 场景首选)

typescript 复制代码
@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 单向就够。要写「子显示父数据不改」时用这个------单向数据流清晰,子改不影响父状态边界。

typescript 复制代码
@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(双向同步要父管源状态)。

typescript 复制代码
@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 完整代码

typescript 复制代码
// 篇 57 demo:@Prop 单向 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 状态哲学核心!

相关推荐
JoyT1 小时前
面向Agent系统的Java后端知识总览(上)
后端
AI编程实验室1 小时前
用 npm + Three.js 做一颗西瓜:把夏天的清凉感放进浏览器
前端·后端·ai编程
SimonKing1 小时前
别再盲目跑测试了,用 JaCoCo 告诉你哪些代码根本没被覆盖
java·后端·程序员
渣波1 小时前
基于 Milvus 构建小说知识库 RAG,实现图书智能问答(天龙八部实战)
前端·后端
JavaGuide1 小时前
我最推荐的 4 个 AI 编程 Skills:grill-me、research、diagnosing-bugs、code-review
前端·后端·ai编程
Moment1 小时前
2026 了,前端转 AI 全栈我是这么学的 😍😍😍
前端·后端·面试
CoderF2 小时前
jstack 如何分析死锁:从线程栈到等待环
后端
Conan在掘金2 小时前
ArkTS 进阶之道(5):struct 里为啥不能嵌 struct?终态声明 + 状态边界
后端