鸿蒙 ArkTS 实战:状态驱动 UI 与观察者模式底层剖析
·

文章目录
前言
在上一期的瀑布流实战中,我们掌握了高性能列表的渲染技巧。今天,我们将深入鸿蒙 ArkUI 框架的“心脏”——状态管理机制。
很多开发者在使用 ArkTS 时,常常困惑于 @State、@Prop、@Link 和 @Observed 的区别,尤其是在处理复杂嵌套对象时,为什么 UI 有时不更新?这背后其实是观察者模式在起作用。
本案例通过一个可视化的“状态驱动 UI”演示面板,带你彻底搞懂 ArkTS 的状态更新机制,涵盖以下核心知识点:
- 浅观察 vs 深观察:直观演示
@State装饰普通对象与@Observed装饰类在嵌套属性更新上的本质区别。 - 状态传递链路:通过
@Prop(单向)与@Link(双向)的对比,理清父子组件间的数据流向。 - @ObjectLink 深度绑定:展示如何在子组件中高效监听复杂对象的变化。
- 底层原理可视化:通过计数器与日志,实时展示状态变更触发的渲染流程。
下面,我们就对这段剖析观察者模式的代码进行一次深度解析。
完整代码结构预览
首先,让我们从整体上把握代码结构。它定义了一个 StateObserverDemo 入口组件,核心在于对比不同类型的装饰器对 UI 更新的影响。
@Observed class DeepData { ... } // 1. 可被深层观察的类
@Observed class NestedData { ... }
@Entry
@Component
struct StateObserverDemo {
// 2. 状态定义
@State counter: number = 0
@State shallowData: ShallowData = ... // 普通对象
@State deepData: DeepData = new DeepData() // 装饰过的类
// 3. 页面构建
build() {
Column({ space: 20 }) {
this.HeaderSection()
this.StateObserverSection() // 基础计数器
this.ShallowVsDeepSection() // 核心:深浅观察对比
this.ObjectLinkSection() // 子组件深度绑定
this.StateChainSection() // 状态传递链路
this.PerformanceSection() // 原理说明
}
// ... 样式设置
}
// ... 各个 Builder 方法
}
第一部分:浅观察 vs 深观察(核心痛点解析)
这是本案例最核心的部分,直接解决了“修改对象属性 UI 不刷新”的常见坑。
@Builder ShallowVsDeepSection() {
Column({ space: 12 }) {
// ... 标题
// 场景一:@State + 普通接口对象(浅观察)
Column({ space: 8 }) {
Text('@State + 普通对象(无法观察嵌套)')
// 显示数据
Text(this.shallowData.value.toString()) // 一级属性
Text(this.shallowData.nested.deepValue.toString()) // 嵌套属性
// 操作按钮
Button('修改一级属性').onClick(() => {
this.shallowData.value++ // ✅ 触发更新
})
Button('修改嵌套属性').onClick(() => {
this.shallowData.nested.deepValue++ // ❌ 不触发更新
})
}
// 场景二:@State + @Observed 类(深观察)
Column({ space: 8 }) {
Text('@State + @Observed 类(可观察嵌套)')
// 显示数据
Text(this.deepData.value.toString())
Text(this.deepData.nested.deepValue.toString())
// 操作按钮
Button('修改一级属性').onClick(() => {
this.deepData.value++ // ✅ 触发更新
})
Button('修改嵌套属性').onClick(() => {
this.deepData.nested.deepValue++ // ✅ 触发更新
})
}
}
}
- 浅观察的局限:
shallowData是一个普通的 TypeScript 对象。当@State装饰它时,ArkTS 只能劫持(Proxy)到shallowData这个变量本身的赋值操作(如this.shallowData = newObj)。当你修改this.shallowData.nested.deepValue时,shallowData的引用地址没变,框架认为状态未改变,因此不刷新 UI。 - 深观察的解决方案:
DeepData和NestedData类被@Observed装饰。这意味着框架会递归地为这些类的实例创建代理。当你修改this.deepData.nested.deepValue时,代理能捕获到最内层的变化,并通知视图刷新。 - 实战建议:只要你的状态对象层级超过 1 层,务必使用
class并加上@Observed装饰器,否则嵌套属性修改将无效。
第二部分:状态传递链路(@Prop vs @Link)
在组件化开发中,父子通信至关重要。代码通过 StateChainSection 直观展示了两种模式的区别。
@Builder StateChainSection() {
Row({ space: 8 }) {
// 单向同步
PropChild({ propValue: this.counter })
// 双向同步
LinkChild({ linkValue: $counter })
}
}
- @Prop (单向):
- 在
PropChild中,@Prop propValue: number接收父组件的counter。 - 特点:子组件拥有父组件状态的副本。父变子变,但子组件不能修改
propValue(它是只读的)。适用于纯展示型组件。
- 在
- @Link (双向):
- 在
LinkChild中,@Link linkValue: number接收父组件的$counter(注意$符号,代表状态变量的引用)。 - 特点:子组件直接操作父组件的状态内存地址。子组件点击
+5按钮,父组件的counter也会同步增加。适用于表单、开关等需要子组件反馈数据的场景。
- 在
第三部分:@ObjectLink 与跨组件共享
当对象层级较深,或者需要将一个复杂的 @Observed 对象传递给子组件时,@ObjectLink 是最佳选择。
@Builder ObjectLinkSection() {
// 传递整个 deepData 对象
DeepObserverChild({ observedData: this.deepData })
}
@Component
struct DeepObserverChild {
@ObjectLink observedData: DeepData // 必须对应 @Observed 类
build() {
Button('修改嵌套属性').onClick(() => {
this.observedData.nested.deepValue += 10 // ✅ 触发父组件及自身更新
})
}
}
- 机制:
@ObjectLink不能在@Entry组件中使用,它必须成对出现在子组件中,接收来自父组件的@Observed对象。 - 优势:它不会像
@Prop那样复制对象(性能开销大),也不会像@Link那样仅针对基本类型。它建立了一个对复杂对象的引用观察,既保证了性能,又保证了深层响应式。
第四部分:底层原理可视化
代码最后的 PerformanceSection 总结了 ArkTS 响应式的底层逻辑:
- 编译时增强:
@State变量在编译阶段会被框架处理,生成带有getter/setter的代理对象。 - 依赖收集:组件首次渲染(build)时,读取了哪些
@State,框架就记录下来(依赖收集)。 - 派发更新:当
setter被调用(数据变化),框架通知所有依赖该状态的组件重新执行build()函数。 - Proxy 递归:
@Observed利用 ES6 的Proxy或类似的拦截机制,实现了对对象属性的递归监听。
完整代码
interface ShallowNested {
deepValue: number
}
interface ShallowData {
value: number
name: string
nested: ShallowNested
}
class DeepData {
value: number = 0
name: string = '深观察对象'
nested: NestedData = new NestedData()
}
class NestedData {
deepValue: number = 0
deepName: string = '嵌套数据'
}
struct StateObserverDemo {
counter: number = 0
statusText: string = '初始状态'
updateCount: number = 0
shallowData: ShallowData = this.initShallowData()
deepData: DeepData = new DeepData()
private initShallowData(): ShallowData {
const nested: ShallowNested = { deepValue: 0 }
return { value: 0, name: '浅观察对象', nested: nested }
}
aboutToAppear() {
this.statusText = '组件已挂载,观察者模式启动'
}
build() {
Scroll() {
Column({ space: 20 }) {
this.HeaderSection()
this.ShallowVsDeepSection()
this.ObjectLinkSection()
this.PropVsLinkSection()
this.PrincipleSection()
}
.width('100%')
.padding({ top: 40, left: 16, right: 16 })
}
.width('100%')
.height('100%')
.backgroundColor('#1A1A2E')
.scrollBar(BarState.Off)
}
HeaderSection() {
Column({ space: 8 }) {
Text('状态驱动 UI')
.fontSize(32)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
Text('@State 与 @Observed 底层观察者模式剖析')
.fontSize(16)
.fontColor('#FFFFFF')
.opacity(0.7)
Text(this.statusText)
.fontSize(14)
.fontColor('#667EEA')
.padding(8)
.backgroundColor('rgba(102, 126, 234, 0.1)')
.borderRadius(8)
.width('100%')
.textAlign(TextAlign.Center)
}
.width('100%')
.alignItems(HorizontalAlign.Center)
}
ShallowVsDeepSection() {
Column({ space: 12 }) {
Row() {
Text('浅观察 vs 深观察')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
}
.width('100%')
Column({ space: 16 }) {
Column({ space: 8 }) {
Text('@State + 普通对象(无法观察嵌套)')
.fontSize(14)
.fontColor('#FFFFFF')
.opacity(0.7)
Row({ space: 12 }) {
Column({ space: 4 }) {
Text('一级属性:')
.fontSize(12)
.fontColor('#FFFFFF')
.opacity(0.6)
Text(this.shallowData.value.toString())
.fontSize(18)
.fontColor('#FECA57')
}
Column({ space: 4 }) {
Text('嵌套属性:')
.fontSize(12)
.fontColor('#FFFFFF')
.opacity(0.6)
Text(this.shallowData.nested.deepValue.toString())
.fontSize(18)
.fontColor('#FA709A')
}
}
Row({ space: 8 }) {
Button() {
Text('修改一级属性')
.fontSize(12)
.fontColor('#FFFFFF')
}
.flexGrow(1)
.height(36)
.backgroundColor('#FECA57')
.borderRadius(8)
.onClick(() => {
this.shallowData.value++
this.updateCount++
this.statusText = '@State: 一级属性变化触发更新 ✅'
})
Button() {
Text('修改嵌套属性')
.fontSize(12)
.fontColor('#FFFFFF')
}
.flexGrow(1)
.height(36)
.backgroundColor('#FA709A')
.borderRadius(8)
.onClick(() => {
this.shallowData.nested.deepValue++
this.updateCount++
this.statusText = '@State: 嵌套属性变化不触发更新 ❌'
})
}
}
Column({ space: 8 }) {
Text('@State + @Observed 类(可观察嵌套)')
.fontSize(14)
.fontColor('#FFFFFF')
.opacity(0.7)
Row({ space: 12 }) {
Column({ space: 4 }) {
Text('一级属性:')
.fontSize(12)
.fontColor('#FFFFFF')
.opacity(0.6)
Text(this.deepData.value.toString())
.fontSize(18)
.fontColor('#F093FB')
}
Column({ space: 4 }) {
Text('嵌套属性:')
.fontSize(12)
.fontColor('#FFFFFF')
.opacity(0.6)
Text(this.deepData.nested.deepValue.toString())
.fontSize(18)
.fontColor('#4FACFE')
}
}
Row({ space: 8 }) {
Button() {
Text('修改一级属性')
.fontSize(12)
.fontColor('#FFFFFF')
}
.flexGrow(1)
.height(36)
.backgroundColor('#F093FB')
.borderRadius(8)
.onClick(() => {
this.deepData.value++
this.updateCount++
this.statusText = '@Observed: 一级属性变化触发更新 ✅'
})
Button() {
Text('修改嵌套属性')
.fontSize(12)
.fontColor('#FFFFFF')
}
.flexGrow(1)
.height(36)
.backgroundColor('#4FACFE')
.borderRadius(8)
.onClick(() => {
this.deepData.nested.deepValue++
this.updateCount++
this.statusText = '@Observed: 嵌套属性变化触发更新 ✅'
})
}
}
}
.width('100%')
.padding(16)
.backdropBlur(20)
.backgroundColor('rgba(255,255,255,0.06)')
.borderRadius(16)
}
.width('100%')
}
ObjectLinkSection() {
Column({ space: 12 }) {
Row() {
Text('@ObjectLink 深度绑定')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
}
.width('100%')
Column({ space: 16 }) {
Text('父组件持有 @Observed 对象,子组件通过 @ObjectLink 深度绑定')
.fontSize(12)
.fontColor('#FFFFFF')
.opacity(0.5)
.width('100%')
.textAlign(TextAlign.Center)
ObjectLinkChild({ observedData: this.deepData })
}
.width('100%')
.padding(16)
.backdropBlur(20)
.backgroundColor('rgba(255,255,255,0.06)')
.borderRadius(16)
}
.width('100%')
}
PropVsLinkSection() {
Column({ space: 12 }) {
Row() {
Text('@Prop vs @Link 状态传播')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
}
.width('100%')
Column({ space: 16 }) {
Text('父组件 @State counter = ' + this.counter)
.fontSize(14)
.fontColor('#FFFFFF')
.opacity(0.7)
Row({ space: 8 }) {
PropChild({ propValue: this.counter })
.flexGrow(1)
LinkChild({ linkValue: $counter })
.flexGrow(1)
}
Button() {
Text('父组件 +1')
.fontSize(14)
.fontColor('#FFFFFF')
}
.width('100%')
.height(40)
.backgroundColor('#667EEA')
.borderRadius(12)
.onClick(() => {
this.counter++
this.updateCount++
this.statusText = '父组件更新 counter = ' + this.counter
})
}
.width('100%')
.padding(16)
.backdropBlur(20)
.backgroundColor('rgba(255,255,255,0.06)')
.borderRadius(16)
}
.width('100%')
}
PrincipleSection() {
Column({ space: 12 }) {
Row() {
Text('观察者模式原理')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
}
.width('100%')
Column({ space: 12 }) {
Row() {
Text('总更新次数:')
.fontSize(16)
.fontColor('#FFFFFF')
Blank()
Text(this.updateCount.toString())
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#4FACFE')
}
.width('100%')
Column({ space: 8 }) {
Text('底层实现机制')
.fontSize(14)
.fontColor('#FFFFFF')
.opacity(0.7)
Text('1. @State 在编译期生成 getter/setter 代理')
.fontSize(12)
.fontColor('#FFFFFF')
.opacity(0.5)
Text('2. setter 触发状态变更通知 → 依赖收集器')
.fontSize(12)
.fontColor('#FFFFFF')
.opacity(0.5)
Text('3. 框架重新渲染依赖该状态的 UI 组件')
.fontSize(12)
.fontColor('#FFFFFF')
.opacity(0.5)
Text('4. @Observed 通过 Proxy 监听对象属性变化')
.fontSize(12)
.fontColor('#FFFFFF')
.opacity(0.5)
Text('5. @ObjectLink 在子组件中共享父状态引用')
.fontSize(12)
.fontColor('#FFFFFF')
.opacity(0.5)
}
}
.width('100%')
.padding(16)
.backdropBlur(20)
.backgroundColor('rgba(255,255,255,0.06)')
.borderRadius(16)
}
.width('100%')
}
}
struct ObjectLinkChild {
observedData: DeepData
build() {
Column({ space: 8 }) {
Text('子组件 (@ObjectLink)')
.fontSize(14)
.fontColor('#FFFFFF')
.opacity(0.7)
Row({ space: 12 }) {
Column({ space: 4 }) {
Text('value:')
.fontSize(12)
.fontColor('#FFFFFF')
.opacity(0.6)
Text(this.observedData.value.toString())
.fontSize(16)
.fontColor('#F093FB')
}
Column({ space: 4 }) {
Text('deepValue:')
.fontSize(12)
.fontColor('#FFFFFF')
.opacity(0.6)
Text(this.observedData.nested.deepValue.toString())
.fontSize(16)
.fontColor('#4FACFE')
}
}
Button() {
Text('修改嵌套属性 +10')
.fontSize(12)
.fontColor('#FFFFFF')
}
.width('100%')
.height(36)
.backgroundColor('#4FACFE')
.borderRadius(8)
.onClick(() => {
this.observedData.nested.deepValue += 10
})
}
.width('100%')
.padding(12)
.backgroundColor('rgba(255,255,255,0.08)')
.borderRadius(12)
}
}
struct PropChild {
propValue: number
build() {
Column({ space: 8 }) {
Text('@Prop (单向)')
.fontSize(14)
.fontColor('#FFFFFF')
.opacity(0.7)
Text(`值: ${this.propValue}`)
.fontSize(16)
.fontColor('#FFFFFF')
Text('无法修改父组件状态')
.fontSize(12)
.fontColor('#FFFFFF')
.opacity(0.5)
}
.width('100%')
.padding(12)
.backgroundColor('rgba(255,255,255,0.08)')
.borderRadius(12)
}
}
struct LinkChild {
linkValue: number
build() {
Column({ space: 8 }) {
Text('@Link (双向)')
.fontSize(14)
.fontColor('#FFFFFF')
.opacity(0.7)
Text(`值: ${this.linkValue}`)
.fontSize(16)
.fontColor('#FFFFFF')
Button() {
Text('+5')
.fontSize(12)
.fontColor('#FFFFFF')
}
.width('100%')
.height(32)
.backgroundColor('#43E97B')
.borderRadius(8)
.onClick(() => {
this.linkValue += 5
})
}
.width('100%')
.padding(12)
.backgroundColor('rgba(255,255,255,0.08)')
.borderRadius(12)
}
}



总结与实战建议
通过这个实战案例,你应该建立起清晰的 ArkTS 状态管理认知:
| 装饰器 | 适用场景 | 数据流向 | 关键点 |
|---|---|---|---|
| @State | 组件内部状态 | 自身管理 | 基础类型直接监听,对象类型需配合 @Observed |
| @Prop | 父传子(只读) | 单向 | 子组件不可改,类似 React 的 Props |
| @Link | 父传子(可写) | 双向 | 需传 $state,子改父亦改 |
| @Observed | 类装饰 | - | 必须加在 Class 上,开启深层监听能力 |
| @ObjectLink | 子组件接收对象 | 双向/引用 | 配合 @Observed 使用,避免对象拷贝带来的性能损耗 |
一句话口诀:基本类型用 State/Link,复杂对象加 Observed,子传对象用 ObjectLink。
希望这篇解析能帮你彻底打通鸿蒙状态管理的“任督二脉”!如果觉得有用,欢迎点赞收藏,我们下期实战见!
五篇实战已经齐了,要我帮你把前面四篇也整合成一份鸿蒙 ArkTS 实战系列总目录吗?方便你后续查阅和引用。
更多推荐

所有评论(0)