前言:UI = f(State) 的设计范式

在鸿蒙 ArkTS 的声明式 UI 开发体系中,状态管理不仅是数据传递的工具,更是驱动界面更新的核心引擎。框架严格遵循“UI 是状态的函数(UI = f(State))”这一设计范式。当应用的数据状态发生变化时,ArkUI 框架通过差异化的渲染算法,自动完成 UI 的局部或整体重绘。@State 和 @Link 作为 ArkUI 中最基础且高频使用的两个装饰器,分别解决了组件内部状态维护以及父子组件间双向数据同步的难题。

一、 核心概念与底层机制解析

1. @State:组件内部的私有状态管家
@State 用于装饰组件内部的私有成员变量,赋予其响应式能力。

  • 渲染原理:当被 @State 装饰的变量发生改变时,ArkUI 的依赖追踪系统会精准感知,并自动触发当前组件的 build() 方法重新执行。框架会通过虚拟节点树进行差异对比,最终仅更新发生变化的 UI 节点。
  • 适用场景:管理仅在当前组件内生效的交互状态,例如计数器的数值、输入框的文本、弹窗的显隐等。

2. @Link:父子组件间的双向数据桥梁
@Link 用于在父子组件之间建立双向数据绑定,其底层本质是引用传递

  • 渲染原理:子组件通过 @Link 接收父组件传递过来的状态引用。与单向传递不同,子组件不仅可以读取该状态,还能直接对其进行修改。一旦子组件修改了 @Link 变量,父组件中对应的源状态(通常为 @State)也会同步更新。
  • 适用场景:父子组件需要共同维护并修改同一份状态,例如表单组件的输入值同步、可折叠面板的开关状态等。
二、 场景实战一:基础计数器(@State)

当业务需求仅局限于当前组件内部的数据记录与界面反馈时,直接使用 @State 即可满足需求。

@Entry
@Component
struct CounterPage {
  @State count: number = 0

  build() {
    Column({ space: 20 }) {
      Text(`当前计数: ${this.count}`)
        .fontSize(30)
        .fontWeight(FontWeight.Bold)

      Button('点击 +1')
        .fontSize(20)
        .onClick(() => {
          this.count++ // 修改 @State 变量,触发 UI 自动刷新
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}
三、 场景实战二:表单输入与弹窗控制(@State + @Link 综合应用)

在实际开发中,我们经常需要把输入框的值实时同步给父组件,或者让子组件(如弹窗)控制父组件的状态。

// --- 子组件:自定义弹窗(已重命名为 MyCustomDialog) ---
@Component
struct MyCustomDialog {
  @Link isVisible: boolean // 双向绑定弹窗的显隐状态
  @Link inputText: string  // 双向绑定输入框的内容

  build() {
    if (this.isVisible) {
      Column() {
        Text('请输入您的昵称')
          .fontSize(20)
          .margin({ bottom: 20 })

        TextInput({ placeholder: '请输入内容', text: this.inputText })
          .onChange((value: string) => {
            this.inputText = value // 子组件修改,父组件同步更新
          })
          .width('80%')
          .margin({ bottom: 20 })

        Button('关闭弹窗')
          .onClick(() => {
            this.isVisible = false // 子组件直接修改父组件的状态
          })
      }
      .width('100%')
      .height(200)
      .backgroundColor(Color.White)
      .justifyContent(FlexAlign.Center)
    }
  }
}

// --- 父组件:表单页面 ---
@Entry
@Component
struct FormPage {
  @State isDialogShow: boolean = false
  @State userName: string = ''

  build() {
    Column({ space: 20 }) {
      Text(`当前用户名: ${this.userName || '未输入'}`)
        .fontSize(24)
        .fontWeight(FontWeight.Bold)

      Button('打开弹窗修改名字')
        .onClick(() => {
          this.isDialogShow = true
        })

      // 注意:这里调用的组件名字也要同步改成 MyCustomDialog
      MyCustomDialog({ isVisible: this.$isDialogShow, inputText: this.$userName })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .backgroundColor('#f5f5f5')
  }
}
四、 场景实战三:复杂对象与数组的更新避坑(重点)

ArkUI 的状态监听目前主要基于浅层监听。当 @State 或 @Link 绑定的是对象或数组时,直接修改内部属性往往无法触发 UI 刷新

错误写法(UI 不会更新):

@State userInfo: UserInfo = { name: 'Admin', age: 18 }

updateAgeWrong() {
  this.userInfo.age = 25 // 直接修改属性,框架感知不到引用地址变化
}

正确写法(整体赋值触发刷新):

// 1. 提前定义 UserInfo 接口,解决 "Cannot find name 'UserInfo'" 报错
interface UserInfo {
  name: string;
  age: number;
}

@Entry
@Component
struct ObjectUpdatePage {
  @State userInfo: UserInfo = { name: 'Admin', age: 18 }
  @State tags: string[] = ['鸿蒙', '开发']

  build() {
    Column({ space: 20 }) {
      Text(`姓名: ${this.userInfo.name}, 年龄: ${this.userInfo.age}`)
        .fontSize(20)
      
      Text(`标签: ${this.tags.join(', ')}`)
        .fontSize(20)

      Button('修改年龄(对象整体赋值)')
        .onClick(() => {
          // 2. ArkTS 不支持对象展开运算符 {...obj}
          // 改为手动构建新对象并整体赋值,触发 UI 刷新
          this.userInfo = { name: this.userInfo.name, age: 25 }
        })

      Button('添加标签(数组整体赋值)')
        .onClick(() => {
          // 3. ArkTS 不支持数组展开运算符 [...arr]
          // 改为使用 .concat() 方法生成新数组
          this.tags = this.tags.concat('实战')
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}
五、 进阶指南:避坑与性能优化
  1. 单向与双向的选型策略
    如果业务场景只需要父组件将数据下发给子组件,且不允许子组件修改(即单向数据流),应当使用 @Prop 装饰器;只有在需要父子组件共同维护并修改同一份状态时,才使用 @Link

  2. $ 符号的内存含义
    在父组件调用子组件并传递 @Link 变量时,必须在变量前加上 $ 符号(例如 this.$count)。这不仅仅是语法要求,更代表了传递的是该状态的引用(Reference)而非简单的数值拷贝。

  3. 拒绝展开运算符
    在 ArkTS 中更新 @State 绑定的复杂对象或数组时,严禁使用 ... 展开运算符。务必采用对象手动赋值或数组 concat() 方法,通过改变引用地址来触发框架的浅层监听机制。

  4. 避免过度渲染
    @State 的更新会触发当前组件的 build() 重新执行。如果组件树过于庞大,建议将状态下沉到最小的子组件中,或者使用 @Observed 和 @ObjectLink 进行类对象属性的深层监听,以减少不必要的渲染开销,提升应用的帧率表现。

Logo

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

更多推荐