请添加图片描述

前言

在上一期的瀑布流实战中,我们掌握了高性能列表的渲染技巧。今天,我们将深入鸿蒙 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
  • 深观察的解决方案DeepDataNestedData 类被 @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 响应式的底层逻辑:

  1. 编译时增强@State 变量在编译阶段会被框架处理,生成带有 getter/setter 的代理对象。
  2. 依赖收集:组件首次渲染(build)时,读取了哪些 @State,框架就记录下来(依赖收集)。
  3. 派发更新:当 setter 被调用(数据变化),框架通知所有依赖该状态的组件重新执行 build() 函数。
  4. 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 实战系列总目录吗?方便你后续查阅和引用。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐