鸿蒙开发-状态管理

自定义构建函数 #

@Builder #

@Builder

  • 组件内
@Builder MyBuilderFunction() {}

this.MyBuilderFunction()
  • 全局
@Builder function MyGlobalBuilderFunction() {}

MyGlobalBuilderFunction()
  • 参数传递
@Builder MyBuilderFunction( title: string ) {}

this.MyBuilderFunction('Title')

反正就跟自定义控件差不多太多? 使用支持传参使其更加灵活

MyBuilderFunction(params: { title: string }){}

@BuilderParam #

这个简单, 跟compose 差不多太多

@Component
struct PanelComp {
  title: string
  more: string
  @BuilderParam
  panelContent: () => void
  @BuilderParam
  panelFooter: () => void

  build() {
    Column() {
      Row() {
        Text(this.title)
          .layoutWeight(1)
          .fontWeight(600)
        Row() {
          Text(this.more)
            .fontSize(14)
            .fontColor('#666666')
          Image($r('app.media.ic_public_arrow_right'))
            .width(16)
            .fillColor('#666666')
        }
      }
      .padding(10)

      Row() {
        this.panelContent()
      }
      .height(100)
      Row() {
        this.panelFooter()
      }
      .height(50)
    }
    .borderRadius(12)
    .backgroundColor('#fff')
  }
}

@Entry
@Component
struct Index {
  @Builder
  ContentBuilderA() {
    Text('评价内容')
  }
  @Builder
  FooterBuilderA() {
    Text('评价底部')
  }

  build() {
    Column() {
      GridRow({ columns: 2, gutter: 15 }) {
        GridCol({ span: 2 }) {
          PanelComp({
            title: '评价(2000+)',
            more: '好评率98%',
            panelFooter: this.FooterBuilderA,
            panelContent: this.ContentBuilderA
          })
        }

        // GridCol() {
        //   PanelComp({ title: '推荐', more: '查看全部' }){
        //     Text('推荐内容')
        //   }
        // }
        //
        // GridCol() {
        //   PanelComp({ title: '体验', more: '4 条测评' }){
        //     Text('体验内容')
        //   }
        // }
      }
    }
    .height('100%')
    .padding(15)
    .backgroundColor('#f5f5f5')
  }
}

状态共享 #

父子单向 #

@Prop 装饰的变量可以和父组件建立单向的同步关系。可变,但是不会传回父组件

@Entry
@Component
struct Index {

  @State
  money: number = 0

  build() {
    Column({ space: 20 }){
      Text('父组件:' + this.money)
        .onClick(() => {
          this.money ++
        })
      Child({ money: this.money })
    }
    .width('100%')
    .height('100%')
    .alignItems(HorizontalAlign.Center)
    .justifyContent(FlexAlign.Center)
  }
}

@Component
struct Child {

  @Prop
  money: number

  build() {
    Text('子组件:' + this.money)
      .onClick(() => {
        this.money ++
      })
  }
}

注意: @Prop支持类型 string、number、boolean、enum 类型

父子共享 #

子组件中被@Link装饰的变量与其父组件中对应的数据源建立双向数据绑定。

  1. 简单类型 string number boolean enum
@Entry
@Component
struct Index {

  @State
  money: number = 0

  build() {
    Column({ space: 20 }){
      Text('父组件:' + this.money)
        .onClick(() => {
          this.money ++
        })
      Child({ money: $money })
    }
    .width('100%')
    .height('100%')
    .alignItems(HorizontalAlign.Center)
    .justifyContent(FlexAlign.Center)
  }
}

@Component
struct Child {

  @Link
  money: number

  build() {
    Text('子组件:' + this.money)
      .onClick(() => {
        this.money ++
      })
  }
}
  1. 复杂类型 Object class
class Person {
  name: string
  age: number
}

@Entry
@Component
struct Index {

  @State
  person: Person = { name: 'jack', age: 18 }

  build() {
    Column({ space: 20 }){
      Text('父组件:' + `${this.person.name} 今年 ${ this.person.age } 岁`)
        .onClick(() => {
          this.person.age ++
        })
      Child({ person: $person })
    }
    .width('100%')
    .height('100%')
    .alignItems(HorizontalAlign.Center)
    .justifyContent(FlexAlign.Center)
  }
}

@Component
struct Child {

  @Link
  person: Person

  build() {
    Text('子组件:' +  `${this.person.name} 今年 ${ this.person.age } 岁`)
      .onClick(() => {
        this.person.age ++
      })
  }
}

注意: 父组件传值的时候需要 this. 改成 $,子组件 @Link 修饰数据

后代组件 #

@Provide@Consume,应用于与后代组件的双向数据同步,应用于状态数据在多个层级之间传递的场景。

  • 通过相同的变量名绑定
@Entry
@Component
struct Index {
  @Provide
  money: number = 0

  build() {
    Column({ space: 20 }) {
      Text('父组件:' + this.money)
        .onClick(() => {
          this.money++
        })
      Parent()
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

@Component
struct Parent {
  @Consume
  money: number

  build() {
    Column({ space: 20 }) {
      Text('父组件:' + this.money)
        .onClick(() => {
          this.money++
        })
      Child()
    }
  }
}

@Component
struct Child {
  @Consume
  money: number

  build() {
    Text('子组件:' + this.money)
      .onClick(() => {
        this.money++
      })
  }
}

状态监听器 #

如果开发者需要关注某个状态变量的值是否改变,可以使用 @Watch 为状态变量设置回调函数。

  • @State、@Prop、@Link 等装饰器在 @Watch 装饰之前
import promptAction from '@ohos.promptAction'

@Component
struct Child {
  @Prop
  @Watch('onActiveIndex')
  activeIndex: number

  onActiveIndex() {
    promptAction.showToast({ message: '监听变化' })
  }

  build() {
    Column() {
      Text('Child' + this.activeIndex)
    }
  }
}

@Entry
@Component
struct Index {
  @State activeIndex: number = 0

  onChange (index: number) {
    this.activeIndex = index
    promptAction.showToast({ message: '点击' })
  }

  build() {
    Navigation() {
      Child({ activeIndex: this.activeIndex })
    }.toolBar({
      items: [
        { value: '首页', action: () => this.onChange(0) },
        { value: '我的', action: () => this.onChange(1) },
      ]
    })
  }
}

tip: 在第一次初始化的时候,@Watch装饰的方法不会被调用

修改嵌套对象的数据了更新UI

  • class 数据模拟需要定义通过构造函数,使用 @Observed 修饰这个类
  • 初始化数据:需要通过初始化构造函数的方式添加
  • 通过 @ObjectLink 关联对象,可以直接修改被关联对象来更新UI

来看看我自己写的一个沟施例子吧

@Entry
@Component
export default struct Index {
  students = [
    new Student({
      name: "张三",
      gender: 1,
      age: 12
    }),
    new Student({
      name: "李四",
      gender: 1,
      age: 13
    }),
    new Student({
      name: "小红",
      gender: 0,
      age: 13
    })
  ]

  build() {
    Column() {
      ForEach(this.students, (s: Student) => {
        Item({s})

      })
    }
  }
}

@Component
struct Item {
  @ObjectLink
  s: Student

  OnLike() {
    this.s.age++
  }

  build() {
    Row() {
      Text(`姓名: ${this.s.name} 年龄: ${this.s.age}`)
        .onClick(() => {
          this.OnLike()
        })
    }.width('100%')
    .backgroundColor("#f5f5f5")
    .borderRadius(5)
  }
}

@Observed
class Student {
  gender: number
  age: number
  name: string

  constructor(s: Student) {
    for (const key in s) {
      this[key] = s[key]
    }
  }
}

应用状态 #

UIAbility内状态-LocalStorage #

LocalStorage 是页面级的UI状态存储,通过 @Entry 装饰器接收的参数可以在页面内共享同一个 LocalStorage 实例。 LocalStorage 也可以在 UIAbility 内,页面间共享状态。

  1. 页面内共享
  • 创建 LocalStorage 实例:const storage = new LocalStorage({ key: value })
  • 单向 @LocalStorageProp('user') 组件内可变
  • 双向 @LocalStorageLink('user')

class User {
  name?: string
  age?: number
}
const storage = new LocalStorage({
  user: { name: 'jack', age: 18 }
})

@Entry(storage)
@Component
struct Index {
  @LocalStorageProp('user')
  user: User = {}

  build() {
    Column({ space: 15 }){
      Text('Index:')
      Text(this.user.name + this.user.age)
      Divider()
      ChildA()
      Divider()
      ChildB()
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

@Component
struct ChildA {
  @LocalStorageProp('user')
  user: User = {}

  build() {
    Column({ space: 15 }){
      Text('ChildA:')
      Text(this.user.name + this.user.age)
        .onClick(()=>{
          this.user.age ++
        })
    }
  }
}

@Component
struct ChildB {
  @LocalStorageLink('user')
  user: User = {}

  build() {
    Column({ space: 15 }){
      Text('ChildB:')
      Text(this.user.name + this.user.age)
        .onClick(()=>{
          this.user.age ++
        })
    }
  }
}
  1. 页面间共享
  • UIAbility 创建 LocalStorage 通过 loadContent 提供给加载的窗口
  • 在页面使用 const storage = LocalStorage.GetShared() 得到实例,通过 @Entry(storage) 传入页面

entryAbility/EntryAbility.ts

+  storage = new LocalStorage({
+    user: { name: 'jack', age: 18 }
+  })

  onWindowStageCreate(windowStage: window.WindowStage) {
    // Main window is created, set main page for this ability
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');

+    windowStage.loadContent('pages/Index', this.storage , (err, data) => {
      if (err.code) {
        hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
        return;
      }
      hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
    });
  }

models/index.ets

export class User {
  name?: string
  age?: number
}

pages/Index.ets

import { User } from '../models'
const storage = LocalStorage.GetShared()

@Entry(storage)
@Component
struct Index {
  @LocalStorageProp('user')
  user: User = {}

  build() {
    Column({ space: 15 }) {
      Text('Index:')
      Text(this.user.name + this.user.age)
        .onClick(()=>{
          this.user.age ++
        })
      Navigator({ target: 'pages/OtherPage' }){
        Text('Go Other Page')
      }
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

pages/OtherPage.ets

import { User } from '../models'
const storage = LocalStorage.GetShared()

@Entry(storage)
@Component
struct OtherPage {
  @LocalStorageLink('user')
  user: User = {}

  build() {
    Column({ space: 15 }) {
      Text('OtherPage:')
      Text(this.user.name + this.user.age)
        .onClick(()=>{
          this.user.age ++
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

应用状态-AppStorage #

AppStorage 是应用全局的UI状态存储,是和应用的进程绑定的,由UI框架在应用程序启动时创建,为应用程序UI状态属性提供中央存储。

  • 如果是初始化使用 AppStorage.SetOrCreate(key,value)
  • 单向 @StorageProp('user') 组件内可变
  • 双向 @StorageLink('user') 全局均可变
  1. 通过UI装饰器使用
import { User } from '../models'

AppStorage.SetOrCreate<User>('user', { name: 'jack', age: 18 })

@Entry
@Component
struct Index {
  @StorageProp('user')
  // 可忽略,编辑器类型错误
  user: User = {}

  build() {
    Column({ space: 15 }) {
      Text('Index:')
      Text(this.user.name + this.user.age)
        .onClick(() => {
          this.user.age++
        })
      Divider()
      ChildA()
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

@Component
struct ChildA {
  @StorageLink('user')
  user: User = {}

  build() {
    Column({ space: 15 }){
      Text('ChildA:')
      Text(this.user.name + this.user.age)
        .onClick(()=>{
          this.user.age ++
        })
    }
  }
}
  1. 通过逻辑使用
  • AppStorage.Get<ValueType>(key) 获取数据
  • AppStorage.Set<ValueType>(key,value) 覆盖数据
  • const link: SubscribedAbstractProperty<ValueType> = AppStorage.Link(key) 覆盖数据
    • link.set(value) 修改
    • link.get() 获取
import promptAction from '@ohos.promptAction'
import { User } from '../models'

AppStorage.SetOrCreate<User>('user', { name: 'jack', age: 18 })

@Entry
@Component
struct Index {
  @StorageLink('user')
  user: User = {}

  build() {
    Column({ space: 15 }) {
      Text('Index:')
      Text(this.user.name + this.user.age)
        .onClick(() => {
          this.user.age++
        })
      Divider()
      Text('Get()')
        .onClick(() => {
          // 仅获取
          const user = AppStorage.Get<User>('user')
          promptAction.showToast({
            message: JSON.stringify(user)
          })
        })
      Text('Set()')
        .onClick(() => {
          // 直接设置
          AppStorage.Set<User>('user', {
            name: 'tom',
            age: 100
          })
          // 观察页面更新没
        })
      Text('Link()')
        .onClick(() => {
          // 获取user的prop
          const user: SubscribedAbstractProperty<User> = AppStorage.Link('user')
          user.set({
            name: user.get().name,
            // 获取后修改
            age: user.get().age + 1
          })
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

状态持久化-PersistentStorage #

PersistentStorage 将选定的 AppStorage 属性保留在设备磁盘上。

DETAILS UI和业务逻辑不直接访问 PersistentStorage 中的属性,所有属性访问都是对 AppStorage 的访问,AppStorage 中的更改会自动同步到 PersistentStorage

WARNING

  • 支持:number, string, boolean, enum 等简单类型;
  • 如果:要支持对象类型,可以转换成json字符串
  • 持久化变量最好是小于2kb的数据,如果开发者需要存储大量的数据,建议使用数据库api。
  1. 简单数据类型的持久化,和获取和修改
import { User } from '../models'

PersistentStorage.PersistProp('count', 100)

@Entry
@Component
struct Index {
  @StorageLink('count')
  count: number = 0

  build() {
    Column({ space: 15 }) {
      Text(this.count.toString())
        .onClick(() => {
          this.count++
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}
  1. 复杂数据类型的持久化,和获取和修改
import { User } from '../models'

PersistentStorage.PersistProp('count', 100)

@Entry
@Component
struct Index {
  @StorageLink('count')
  count: number = 0

  build() {
    Column({ space: 15 }) {
      Text(this.count.toString())
        .onClick(() => {
          this.count++
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}