HarmonyOS 5开发从入门到精通(四):状态管理与数据绑定

HarmonyOS 5开发从入门到精通(四):状态管理与数据绑定

一、状态管理基础概念

在HarmonyOS应用开发中,状态管理是声明式UI的核心机制。状态(State)是驱动UI更新的数据,当状态发生变化时,框架会自动重新渲染相关的UI部分。

1.1 状态与视图的关系

状态管理的基本原理是:UI = f(State),即UI是状态的函数。当状态改变时,UI会自动更新。

状态变量vs常规变量

  • 状态变量:被装饰器装饰的变量,变化时触发UI更新
  • 常规变量:普通变量,变化不会引起UI刷新

二、组件级状态管理

2.1 @State:组件内部状态

@State是组件内部的状态管理装饰器,用于管理组件的私有状态。

@Entry
@Component
struct CounterExample {
  @State count: number = 0
  @State isActive: boolean = false
  
  build() {
    Column({ space: 20 }) {
      Text(`计数: ${this.count}`)
        .fontSize(24)
        .fontColor(this.isActive ? '#007DFF' : '#666')
      
      Button('增加计数')
        .onClick(() => {
          this.count++
          this.isActive = true
        })
        .width(200)
      
      Button('重置')
        .onClick(() => {
          this.count = 0
          this.isActive = false
        })
        .width(200)
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

@State的特点

  • 组件私有,只能在组件内部访问
  • 支持基本类型(number、string、boolean)和复杂类型
  • 变化时自动触发UI更新

2.2 @Prop:父子组件单向同步

@Prop用于父组件向子组件传递数据,建立单向数据流。

// 子组件
@Component
struct ProgressBar {
  @Prop progress: number
  @Prop color: Color = Color.Blue
  
  build() {
    Column() {
      Text(`进度: ${this.progress}%`)
        .fontSize(16)
        .margin({ bottom: 8 })
      
      Stack() {
        // 背景条
        Rectangle()
          .width('100%')
          .height(8)
          .fill('#E5E5E5')
          .borderRadius(4)
        
        // 进度条
        Rectangle()
          .width(`${this.progress}%`)
          .height(8)
          .fill(this.color)
          .borderRadius(4)
      }
      .width('100%')
      .height(8)
    }
    .width('100%')
  }
}

// 父组件
@Entry
@Component
struct ParentComponent {
  @State currentProgress: number = 30
  
  build() {
    Column({ space: 30 }) {
      Text('下载进度演示')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
      
      ProgressBar({ progress: this.currentProgress })
      
      Slider({
        value: this.currentProgress,
        min: 0,
        max: 100,
        step: 1,
        style: SliderStyle.OutSet
      })
      .width('80%')
      .onChange((value: number) => {
        this.currentProgress = value
      })
      
      Button('随机进度')
        .onClick(() => {
          this.currentProgress = Math.floor(Math.random() * 100)
        })
        .width(200)
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }
}

@Prop的特性

  • 单向数据流:父组件变化影响子组件,但子组件修改不影响父组件
  • 本地修改不会被父组件覆盖
  • 适合展示型组件

2.3 @Link:父子组件双向同步

@Link建立父子组件之间的双向数据绑定。

// 颜色选择器子组件
@Component
struct ColorPicker {
  @Link selectedColor: Color
  private colors: Color[] = [
    Color.Red, Color.Blue, Color.Green, 
    Color.Yellow, Color.Orange, Color.Purple
  ]
  
  build() {
    Column() {
      Text('选择主题颜色')
        .fontSize(18)
        .margin({ bottom: 16 })
      
      Wrap({ spacing: 10 }) {
        ForEach(this.colors, (color, index) => {
          Circle({ width: 40, height: 40 })
            .fill(color)
            .stroke(this.selectedColor === color ? '#333' : '#CCC')
            .strokeWidth(this.selectedColor === color ? 3 : 1)
            .onClick(() => {
              this.selectedColor = color
            })
        })
      }
      .width('100%')
    }
  }
}

// 父组件
@Entry
@Component
struct ThemeSettings {
  @State themeColor: Color = Color.Blue
  
  build() {
    Column({ space: 20 }) {
      Text('主题设置')
        .fontSize(28)
        .fontWeight(FontWeight.Bold)
        .fontColor(this.themeColor)
      
      Rectangle()
        .width('90%')
        .height(120)
        .fill(this.themeColor)
        .borderRadius(12)
        .shadow({ radius: 8, color: '#40000000', offsetX: 0, offsetY: 4 })
      
      ColorPicker({ selectedColor: $themeColor })
      
      Text('当前颜色值: ' + this.themeColor)
        .fontSize(14)
        .fontColor('#666')
    }
    .width('100%')
    .height('100%')
    .padding(20)
    .backgroundColor('#F8F8F8')
  }
}

@Link的特性

  • 双向数据绑定:任何一方修改都会同步到另一方
  • 使用$符号传递引用
  • 适合表单控件和实时同步场景

三、跨组件状态共享

3.1 @Provide和@Consume:跨层级通信

@Provide和@Consume实现祖先组件与后代组件之间的直接通信,避免层层传递。

// 用户上下文提供者
@Entry
@Component
struct UserProvider {
  @Provide username: string = '鸿蒙开发者'
  @Provide userLevel: number = 1
  @Provide isVIP: boolean = false
  
  build() {
    Column() {
      Text('用户信息管理')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 30 })
      
      UserProfile()
      
      Divider()
        .margin({ top: 30, bottom: 20 })
      
      UpgradePanel()
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }
}

// 用户信息显示组件
@Component
struct UserProfile {
  @Consume username: string
  @Consume userLevel: number
  @Consume isVIP: boolean
  
  build() {
    Column() {
      Row() {
        Image($r('app.media.user_avatar'))
          .width(60)
          .height(60)
          .borderRadius(30)
          .margin({ right: 15 })
        
        Column() {
          Text(this.username)
            .fontSize(20)
            .fontWeight(FontWeight.Bold)
          
          Text(`等级: ${this.userLevel}`)
            .fontSize(14)
            .fontColor('#666')
          
          if (this.isVIP) {
            Text('VIP会员')
              .fontSize(12)
              .fontColor('#FF6B00')
              .backgroundColor('#FFF0E6')
              .padding({ left: 8, right: 8, top: 2, bottom: 2 })
              .borderRadius(4)
          }
        }
        .alignItems(HorizontalAlign.Start)
      }
      .width('100%')
      .justifyContent(FlexAlign.Start)
    }
  }
}

// 升级面板组件
@Component
struct UpgradePanel {
  @Consume username: string
  @Consume userLevel: number
  @Consume isVIP: boolean
  
  build() {
    Column() {
      Text('账户升级')
        .fontSize(18)
        .fontWeight(FontWeight.Medium)
        .margin({ bottom: 15 })
      
      Button(this.isVIP ? '续费VIP' : '开通VIP')
        .width(200)
        .backgroundColor(this.isVIP ? '#FF6B00' : '#007DFF')
        .onClick(() => {
          // VIP状态切换逻辑
        })
      
      if (!this.isVIP) {
        Text('开通VIP享受更多特权')
          .fontSize(12)
          .fontColor('#999')
          .margin({ top: 8 })
      }
    }
    .width('100%')
    .padding(20)
    .backgroundColor(Color.White)
    .borderRadius(12)
    .shadow({ radius: 2, color: '#10000000', offsetX: 0, offsetY: 1 })
  }
}

3.2 @Observed和@ObjectLink:复杂对象观察

对于嵌套对象,需要使用@Observed和@ObjectLink来观察内部属性变化。

// 定义可观察的类
@Observed
class UserSettings {
  theme: string = 'light'
  fontSize: number = 16
  notifications: boolean = true
  
  constructor(theme: string, fontSize: number, notifications: boolean) {
    this.theme = theme
    this.fontSize = fontSize
    this.notifications = notifications
  }
}

@Entry
@Component
struct SettingsApp {
  @State settings: UserSettings = new UserSettings('light', 16, true)
  
  build() {
    Column() {
      Text('应用设置')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 30 })
      
      SettingsEditor({ settings: this.settings })
      
      PreviewPanel({ settings: this.settings })
    }
    .width('100%')
    .height('100%')
    .padding(20)
    .backgroundColor(this.settings.theme === 'light' ? '#FFFFFF' : '#1C1C1E')
  }
}

@Component
struct SettingsEditor {
  @ObjectLink settings: UserSettings
  
  build() {
    Column({ space: 20 }) {
      // 主题选择
      Row() {
        Text('主题模式')
          .fontSize(16)
          .layoutWeight(1)
        
        Toggle({ type: ToggleType.Checkbox, isOn: this.settings.theme === 'dark' })
          .onChange((isOn: boolean) => {
            this.settings = new UserSettings(
              isOn ? 'dark' : 'light',
              this.settings.fontSize,
              this.settings.notifications
            )
          })
      }
      .width('100%')
      
      // 字体大小调整
      Row() {
        Text('字体大小')
          .fontSize(16)
          .layoutWeight(1)
        
        Text(`${this.settings.fontSize}px`)
          .fontSize(14)
          .fontColor('#666')
        
        Button('-')
          .onClick(() => {
            if (this.settings.fontSize > 12) {
              this.settings = new UserSettings(
                this.settings.theme,
                this.settings.fontSize - 2,
                this.settings.notifications
              )
            }
          })
        
        Button('+')
          .onClick(() => {
            if (this.settings.fontSize < 24) {
              this.settings = new UserSettings(
                this.settings.theme,
                this.settings.fontSize + 2,
                this.settings.notifications
              )
            }
          })
      }
      .width('100%')
    }
    .width('100%')
    .padding(20)
    .backgroundColor(this.settings.theme === 'light' ? '#F5F5F5' : '#2C2C2E')
    .borderRadius(12)
  }
}

四、应用级状态管理

4.1 AppStorage:全局状态管理

AppStorage提供应用级别的全局状态管理。

// 初始化全局状态
AppStorage.SetOrCreate('globalTheme', 'light')
AppStorage.SetOrCreate('language', 'zh-CN')
AppStorage.SetOrCreate('isLoggedIn', false)

@Entry
@Component
struct GlobalStateApp {
  @StorageLink('globalTheme') currentTheme: string = 'light'
  @StorageLink('language') currentLanguage: string = 'zh-CN'
  @StorageLink('isLoggedIn') isLoggedIn: boolean = false
  
  build() {
    Column() {
      Text('全局状态管理')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 30 })
      
      // 主题切换
      Row() {
        Text('当前主题: ')
        Text(this.currentTheme)
          .fontColor(this.currentTheme === 'light' ? '#333' : '#FFF')
      }
      .margin({ bottom: 20 })
      
      Button('切换主题')
        .onClick(() => {
          this.currentTheme = this.currentTheme === 'light' ? 'dark' : 'light'
        })
        .width(200)
        .margin({ bottom: 30 })
      
      // 多语言设置
      Row() {
        Text('当前语言: ')
        Text(this.currentLanguage)
      }
      .margin({ bottom: 20 })
      
      Picker({ selected: this.currentLanguage, range: ['zh-CN', 'en-US', 'ja-JP'] })
        .onChange((value: string) => {
          this.currentLanguage = value
        })
        .width(200)
        .margin({ bottom: 30 })
      
      // 登录状态
      Toggle({ type: ToggleType.Checkbox, isOn: this.isLoggedIn })
        .onChange((isOn: boolean) => {
          this.isLoggedIn = isOn
        })
      
      Text(this.isLoggedIn ? '已登录' : '未登录')
        .fontSize(14)
        .fontColor(this.isLoggedIn ? '#52C41A' : '#FF4D4F')
    }
    .width('100%')
    .height('100%')
    .padding(20)
    .backgroundColor(this.currentTheme === 'light' ? '#FFFFFF' : '#1C1C1E')
  }
}

4.2 PersistentStorage:数据持久化

PersistentStorage将AppStorage中的数据持久化到设备本地。

// 在应用启动时初始化持久化存储
PersistentStorage.PersistProp('userPreferences', {
  theme: 'light',
  language: 'zh-CN',
  fontSize: 16,
  notifications: true
})

PersistentStorage.PersistProp('appSettings', {
  autoSave: true,
  cloudSync: false,
  privacyMode: true
})

@Entry
@Component
struct PersistentApp {
  @StorageLink('userPreferences') preferences: any = {}
  @StorageLink('appSettings') settings: any = {}
  
  aboutToAppear() {
    // 应用启动时,持久化的数据会自动加载
    console.log('用户偏好设置:', this.preferences)
    console.log('应用设置:', this.settings)
  }
  
  build() {
    Column({ space: 20 }) {
      Text('数据持久化演示')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
      
      // 用户偏好设置
      Column() {
        Text('用户偏好设置')
          .fontSize(18)
          .fontWeight(FontWeight.Medium)
          .margin({ bottom: 15 })
        
        Row() {
          Text(`主题: ${this.preferences.theme}`)
          Text(`语言: ${this.preferences.language}`)
          Text(`字体: ${this.preferences.fontSize}px`)
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceBetween)
      }
      .width('100%')
      .padding(15)
      .backgroundColor(Color.White)
      .borderRadius(12)
      
      // 应用设置
      Column() {
        Text('应用设置')
          .fontSize(18)
          .fontWeight(FontWeight.Medium)
          .margin({ bottom: 15 })
        
        Row() {
          Text(`自动保存: ${this.settings.autoSave ? '开启' : '关闭'}`)
          Text(`云同步: ${this.settings.cloudSync ? '开启' : '关闭'}`)
          Text(`隐私模式: ${this.settings.privacyMode ? '开启' : '关闭'}`)
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceBetween)
      }
      .width('100%')
      .padding(15)
      .backgroundColor(Color.White)
      .borderRadius(12)
      
      Button('修改设置')
        .onClick(() => {
          // 修改设置会自动持久化
          this.preferences = {
            ...this.preferences,
            theme: this.preferences.theme === 'light' ? 'dark' : 'light',
            fontSize: this.preferences.fontSize + 2
          }
        })
        .width(200)
    }
    .width('100%')
    .height('100%')
    .padding(20)
    .backgroundColor('#F5F5F5')
  }
}

五、状态管理最佳实践

5.1 状态管理方案选择指南

场景 推荐方案 理由
组件内部私有状态 @State 简单直接,响应式更新组件UI
父子组件简单同步 @Link(双向)/ @Prop(单向) 关系明确,数据流清晰
跨多层组件传递 @Provide / @Consume 避免"prop drilling",简化代码
全局UI状态(如主题) AppStorage + @StorageLink 一处修改,全局响应
需要持久化的全局配置 PersistentStorage + AppStorage 数据持久化,应用重启不丢失

5.2 性能优化建议

  1. 避免不必要的渲染
// 错误做法:直接修改对象属性
this.user.profile.name = '新名字' // 不会触发UI更新

// 正确做法:创建新对象
this.user = {
  ...this.user,
  profile: {
    ...this.user.profile,
    name: '新名字'
  }
}
  1. 精细化状态划分
// 不推荐:一个大状态对象
@State userData: {
  profile: any,
  settings: any,
  preferences: any
} = {...}

// 推荐:拆分为多个小状态
@State userProfile: any = {...}
@State userSettings: any = {...}
@State userPreferences: any = {...}
  1. 合理使用@Watch监听器
@Component
struct SmartComponent {
  @State @Watch('onCountChange') count: number = 0
  @State doubledCount: number = 0
  
  onCountChange() {
    // 只有count变化时才执行计算
    this.doubledCount = this.count * 2
  }
  
  build() {
    // UI代码
  }
}

六、总结

通过本篇教程,您已经掌握了:

状态管理的基本概念和核心原理

组件级状态管理(@State、@Prop、@Link)的使用

跨组件状态共享(@Provide/@Consume、@Observed/@ObjectLink)

应用级状态管理(AppStorage、PersistentStorage)

性能优化和最佳实践

关键知识点回顾

  • 状态驱动UI更新是声明式开发的核心
  • 根据数据流方向选择合适的装饰器
  • 复杂对象需要使用@Observed和@ObjectLink
  • 全局状态使用AppStorage,持久化使用PersistentStorage

下一篇我们将学习页面路由与导航,掌握多页面应用的开发技巧。建议您动手实践本文中的各种状态管理场景,特别是综合案例部分,这将帮助您深入理解状态管理的各种模式和使用场景。

posted @ 2025-12-23 18:08  奇崽  阅读(0)  评论(0)    收藏  举报