HarmonyOS 4.0——状态管理及Demo

106 阅读2分钟

状态管理

在声明式UI编程框架中,UI是程序状态的运行结果,用户构建了一个UI模型,其中应用的运行时的状态是参数。当参数改变时,UI作为返回结果,也将进行对应的改变。这些运行时的状态变化所带来的UI的重新渲染,在ArkUI中统称为状态管理机制

组件状态管理

@State装饰器:组件内状态

@State装饰的变量,或称为状态变量,一旦变量拥有了状态属性,就和自定义组件的渲染绑定起来。当状态改变时,UI会发生对应的渲染改变。

  • 使用
@State xxx: number = 1
  • 限制

  • @State装饰的变量是私有的,只能从组件内部访问

  • 声明时必须指定其类型和本地初始化,不然编译器报错

  • 数据类型不可以定义any,不然预览器日志报错

  • 变量类型可以是Object、class、string、number、boolean、enum类型,以及这些类型的数组,不支持嵌套类型(建议@Observed装饰器和@ObjectLink装饰器)

  • Demo

StatePage.ets

/*
 * @State装饰器:组件内状态
 * 注意:
 * 1.声明时,必须指定类型和本地初始化,不然编译器报错
 * 2.数据类型不可以定义any,不然预览器日志报错
 * */
@Entry
@Component
struct StatePage {
  // num: number = 1 // 不同步渲染
  // 定义状态变量
  @State num: number = 1 // 同步渲染

  build() {
    Row() {
      Column() {
        Text('NUM' + this.num) // 使用状态变量
          .fontSize("30vp")
        Button('点击NUM++')
          .onClick(() => {
            this.num++ // 使用状态变量
            console.log('this.num', this.num)
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

StateCannotNestedObjectPage.ets (嵌套类型不可以Demo)

/*
 * @State不适用嵌套类对象属性变化
 * */
class Person {
  name: string
  age: number
  friend: Person

  constructor(name: string, age: number, friend?: Person) {
    this.name = name
    this.age = age
    this.friend = friend
  }
}

@Entry
@Component
struct StateCannotNestedObjectPage {
  @State p: Person = new Person('zs', 66, new Person('ls', 55))

  build() {
    Row() {
      Column() {
        Text(`对象的属性:${this.p.name}-${this.p.age}`)
          .fontSize(30)
          .fontWeight(FontWeight.Bold)
          .onClick(() => {
            // 对象的属性修改,视图更新
            this.p.age++
          })

        Text(`嵌套对象的属性:${this.p.friend.name}${this.p.friend.age}`)
          .fontSize(30)
          .fontWeight(FontWeight.Bold)
          .onClick(() => {
            // 嵌套对象的属性修改,视图不更新(数据更新了)
            this.p.friend.age++
          })

      }
      .width('100%')
    }
    .height('100%')
  }
}

@Prop装饰器:父子单向同步

@Prop装饰的变量可以和父组件建立单向的同步关系。

  • 使用
@Prop xxx: number = 1
  • 限制

  • @Prop变量允许在本地修改,但修改后的变化不会同步回父组件

  • 父组件中对应的@State装饰的变量被修改后,子组件本地修改的@Prop装饰的相关变量值将被覆盖

  • 不能在@Entry装饰的自定义组件中使用

  • Demo

/*
 * @Prop装饰器:父子单向同步
 * 注意:
 * 1.不能在@Entry装饰的自定义组件中使用
 * */
@Entry
@Component
struct PropPage {
  @State name: string = 'zs'

  build() {
    Row() {
      Column() {
        Text(this.name)
        Button('父组件按钮:修改name')
          .onClick(() => {
            this.name = this.name === 'zs' ? 'ls' : 'zs'
          })
        PropPage_prop({ content_prop: this.name }) //使用@Prop的子组件
      }
      .width('100%')
      .border({ width: 3, color: 0x317AF7, radius: 10, style: BorderStyle.Dotted })
    }
    .height('100%')
  }
}

// @Prop 装饰的变量是可变的,但修改不会同步回父组件。
// State ---> Prop  单向
@Component
struct PropPage_prop {
  @Prop content_prop: string

  build() {
    Column() {
      Text('Prop:' + this.content_prop)
      Button('子组件按钮:修改Prop数据')
        .onClick(() => {
          this.content_prop = '改变Prop数据' //子组件数据变了,父组件数据不变
        })
    }.border({ width: 3, color: Color.Pink, radius: 10, style: BorderStyle.Dotted })
  }
}

@Link装饰器:父子双向同步

@Link装饰的变量与其父组件中的数据源共享相同的值。

  • 使用
@Link xxx: number = 1
  • 限制

  • @Link装饰器不能在@Entry装饰的自定义组件中使用

  • 变量类型可以是Object、class、string、number、boolean、enum类型,以及这些类型的数组,不支持嵌套类型(建议@Observed装饰器和@ObjectLink装饰器)

  • Demo

/*
 * @Link装饰器:父子双向同步
 * 注意:
 * 1.不能在@Entry装饰的自定义组件中使用
 * */
@Entry
@Component
struct LinkPage {
  @State name: string = 'zs'

  build() {
    Row() {
      Column() {
        Text(this.name)
        Button('父组件按钮:修改name')
          .onClick(() => {
            this.name = this.name === 'zs' ? 'ls' : 'zs'
          })

        Divider()
        // LinkPage_link({ content_link: this.name }) //Preview正常,但本行报红
        // 如果是 State <---> Link 参数传递使用$变量名,而不是this.变量
        LinkPage_link({ content_link: $name })
      }
      .width('100%')
      .border({ width: 3, color: 0x317AF7, radius: 10, style: BorderStyle.Dotted })
    }
    .height('100%')
  }
}

// @Link 装饰的状态数据 同步父组件
// State <---> Prop
@Component
struct LinkPage_link {
  @Link content_link: string

  build() {
    Column() {
      Text('Link:' + this.content_link)
      Button('子组件按钮:修改Link数据').onClick(() => {
        this.content_link = '改变Link数据'
      })
    }.border({ width: 3, color: Color.Pink, radius: 10, style: BorderStyle.Dotted })
  }
}

@Provide装饰器和@Consume装饰器:与后代组件双向同步

@Provide和@Consume,应用于与后代组件双向数据同步,应用于状态数据在多个层级之间传递的场景。不同于上文提到的父子组件之间通过命名参数机制传递,@Provide和@Consume摆脱参数传递机制的束缚,实现跨层级传递。

其中@Provide装饰的变量是在祖先节点中,可以理解为被“提供”给后代的状态变量。@Consume装饰的变量是在后代组件中,去“消费(绑定)”祖先节点提供的变量。

  • 使用
@Provide('xxx') xxxx: string = 'zs'
@Consume xxxx: string 
  • 限制

  • @Provide和@Consume必须绑定相同的变量名、数据类型 (日志报错;无报错?)

  • 不允许在同一个自定义组件内,包括其子组件中声明多个同名或者同别名的@Provide装饰的变量 (P33 日志报错)

  • Demo

/*
 * @Provide装饰器和@Consume装饰器:与后代组件双向同步
 * 注意:
 * 1.@Provide和@Consume必须绑定相同的变量名、数据类型 (日志报错;无报错?)
 * 2.不允许在同一个自定义组件内,包括其子组件中声明多个同名或者同别名的@Provide装饰的变量 (P33 日志报错)
 * */
@Entry
@Component
struct ProvideConsumePage {
  // @Provide('别名') 变量名
  @Provide('alias') message: string = 'zs'

  build() {
    Row() {
      Column({ space: 20 }) {
        Text('父组件内容:' + this.message).ProvideConsumePage_textStyle()
          .onClick(() => {
            this.message = '父组件修改了'
          })
        Divider()
        //  父调用子
        ProvideConsumePage_son()
      }
      .width('100%')
    }
    .height('100%')
  }
}

// 子组件
@Component
struct ProvideConsumePage_son {
  // @Provide message: string = 'zs' //日志报错
  @Consume alias: string // 使用Provide的别名

  build() {
    Column({ space: 20 }) {
      Text('子组件内容:' + this.alias).ProvideConsumePage_textStyle()
        .onClick(() => {
          this.alias = '子组件修改了'
        })
      Divider()
      //  子调用孙
      ProvideConsumePage_grandson()
    }
  }
}

// 孙组件
@Component
struct ProvideConsumePage_grandson {
  @Consume message: string // 使用Provide的变量名

  build() {
    Column() {
      Text('孙组件内容:' + this.message).ProvideConsumePage_textStyle()
        .onClick(() => {
          this.message = '孙组件修改了'
        })
    }
  }
}

@Extend(Text) function ProvideConsumePage_textStyle() {
  .fontSize(30)
  .fontWeight(FontWeight.Bold)
  .fontColor(0x317AF7)
}

@Observed装饰器和@ObjectLink装饰器:嵌套类对象属性变化

@Observed和@ObjectLink装饰器用于在涉及嵌套对象数组元素为对象的场景中进行双向数据同步。

  • 使用
@Observed
class XxxClass {
}

@Entry
@Component
struct Xxx {
  build() {
    Column() {
      XxxComponent({ xxx: xxx })
    }
  }
}

@Component
struct XxxComponent {
  // 自定义组件形式使用@ObjectLink
  @ObjectLink xxx: XxxClass
  build() {
    Row() {
      Text(this.xxx.xxx)
    }
  }
}
  • 限制

  • 使用@Observed装饰class会改变class原始的原型链,@Observed和其他类装饰器装饰同一个class可能会带来问题。

  • @ObjectLink装饰器不能@Entry装饰的自定义组件中使用。

  • @ObjectLink只能接收被@Observed装饰class的实例。

  • @ObjectLink装饰的变量不能被赋值,如果要使用赋值操作,请使用@Prop。

    复制代码
     // 允许@ObjectLink装饰的数据属性赋值
     this.objLink.a= ...
     // 不允许@ObjectLink装饰的数据自身赋值
     this.objLink= ...
    
    • @Prop装饰的变量和数据源的关系是是单向同步,@Prop装饰的变量在本地拷贝了数据源,所以它允许本地更改,如果父组件中的数据源有更新,@Prop装饰的变量本地的修改将被覆盖
    • @ObjectLink装饰的变量和数据源的关系是双向同步,@ObjectLink装饰的变量相当于指向数据源的指针。如果一旦发生@ObjectLink装饰的变量的赋值,则同步链将被打断
  • Demo

/*
 * @Observed装饰器和@ObjectLink装饰器:嵌套类对象属性变化
 * */
@Observed
class Person2 {
  name: string
  age: number
  friend: Person2

  constructor(name: string, age: number, friend?: Person2) {
    this.name = name
    this.age = age
    this.friend = friend
  }
}

@Entry
@Component
struct ObservedObjectLinkPage {
  index: number = 1
  @State p: Person2 = new Person2('zs', 66, new Person2('ls', 55))

  build() {
    Row() {
      Column() {
        Text(`对象的属性:${this.p.name}-${this.p.age}`)
          .fontSize(30)
          .fontWeight(FontWeight.Bold)
          .onClick(() => {
            // 对象的属性修改,视图更新
            this.p.age++
          })
        ObservedObjectLinkPage_NestedObject({ friend: this.p.friend })

      }
      .width('100%')
    }
    .height('100%')
  }
}

@Component
struct ObservedObjectLinkPage_NestedObject {
  @ObjectLink friend: Person2

  build() {
    Text(`嵌套对象的属性:${this.friend.name}${this.friend.age}`)
      .fontSize(30)
      .fontWeight(FontWeight.Bold)
      .onClick(() => {
        // 嵌套对象的属性修改,视图更新
        this.friend.age++
      })
  }
}

应用状态管理

多个页面的状态数据共享。

LocalStorage:页面级UI状态存储

页面级UI状态存储,通常用于UIAbility内、页面间的状态共享。

  • 应用程序可以创建多个LocalStorage实例,LocalStorage实例可以在页面内共享,也可以通过GetShared接口,实现跨页面、UIAbility实例内共享

  • 组件树的根节点,即被@Entry装饰的@Component,可以被分配一个LocalStorage实例,此组件的所有子组件实例将自动获得对该LocalStorage实例的访问权限

  • 被@Component装饰的组件最多可以访问一个LocalStorage实例和AppStorage,未被@Entry装饰的组件不可被独立分配LocalStorage实例,只能接受父组件通过@Entry传递来的LocalStorage实例。一个LocalStorage实例在组件树上可以被分配给多个组件

  • LocalStorage中的所有属性都是可变的

  • 使用

初始化

let localStorage = new LocalStorage({ 'PropA': 47 });

可从@Component组件访问

@Entry(localStorage)

通过装饰器获取和设置

// 获取
@LocalStorageProp('LocalStorage') varA: number = 0
@LocalStorageLink('LocalStorage') varB: number = 1

// 设置
this.varA += 1

通过localStorage获取和设置

// 获取
localStorage.get<number>('LocalStorage') 
// 设置
localStorage.set<number>('LocalStorage', 666)
  • 限制

  • LocalStorage创建后,命名属性的类型不可更改。后续调用Set时必须使用相同类型的值

  • @LocalStorageProp单向数据同步,@LocalStorageLink双向数据同步

  • 如果LocalStorage给定key的属性发生改变,改变会被同步给@LocalStorageProp,并覆盖掉本地的修改

  • LocalStorage.getShared只在模拟器或者实机上才有效,不能在Preview预览器中使用

  • 应用退出打开,LocalStorage数据都没了

  • Demo

EntryAbility.ts

// 定义LocalStorage
let para: Record<string, number> = { 'globalLocalStorage': 666 };
let localStorage: LocalStorage = new LocalStorage(para);

export default class EntryAbility extends UIAbility {
  // 定义LocalStorage
  storage: LocalStorage = localStorage

  onWindowStageCreate(windowStage: window.WindowStage) {
  // 定义LocalStorage
  windowStage.loadContent('pages/EnvironmentPage', this.storage, (err, data) => {})
  }
}

LocalStoragePage.ets

/*
 * LocalStorage:页面级UI状态存储
 * 注意:
 * 1.LocalStorage创建后,命名属性的类型不可更改。后续调用Set时必须使用相同类型的值
 * 2.@LocalStorageProp单向数据同步,@LocalStorageLink双向数据同步
 * 3.如果LocalStorage给定key的属性发生改变,改变会被同步给@LocalStorageProp,并覆盖掉本地的修改
 * */
import router from '@ohos.router';
// 创建新实例并使用给定对象初始化
let localStorage = new LocalStorage({ 'PropA': 47 });
// 使LocalStorage可从@Component组件访问
@Entry(localStorage)
@Component
struct LocalStoragePage {
  // 先定义的初始值 覆盖后定义的
  @LocalStorageProp('LocalStorage') varA: number = 0
  @LocalStorageLink('LocalStorage') varB: number = 1

  build() {
    Row() {
      Column() {
        Text(`LocalStorage:${this.varA}`).fontSize(30).fontColor(0x0033aa)
        Button('通过LocalStorage修改变量').margin(10)
          .onClick(() => {
            localStorage.set<number>('LocalStorage', localStorage.get<number>('LocalStorage') + 1)
          })
        Button('直接修改@LocalStorageProp修饰变量').margin(10)
          .onClick(() => {
            this.varA += 1
          })
        Button('直接修改@LocalStorageLink修饰变量').margin(10)
          .onClick(() => {
            this.varB += 1
          })
        Divider()
        Button('跳转LocalStorage_Test1').margin(10)
          .onClick(() => {
            router.pushUrl({ url: 'pages/LocalStorage_Test1' })
          })
        Button('跳转LocalStorage_Test2')
          .onClick(() => {
            router.pushUrl({ url: 'pages/LocalStorage_Test2' })
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

LocalStorage_Test1.ets

// LocalStorage.getShared只在模拟器或者实机上才有效,不能在Preview预览器中使用。
// 通过GetShared接口获取stage共享的LocalStorage实例
let localStorage = LocalStorage.GetShared()

@Entry(localStorage)
@Component
struct LocalStorage_Test1 {
  @LocalStorageLink('globalLocalStorage') varA: number = 1;

  build() {
    Column() {
      Text(`globalLocalStorage:${this.varA}`).fontSize(30)
        .onClick(() => {
          this.varA++
        })
    }
  }
}

LocalStorage_Test2.ets

// LocalStorage.getShared只在模拟器或者实机上才有效,不能在Preview预览器中使用。
// 通过GetShared接口获取stage共享的LocalStorage实例
let localStorage2 = LocalStorage.GetShared()

@Entry(localStorage2)
@Component
struct LocalStorage_Test2 {
  @LocalStorageLink('globalLocalStorage') varA: number = 1;

  build() {
    Column() {
      Text(`globalLocalStorage:${this.varA}`).fontSize(30)
        .onClick(() => {
          this.varA--
        })
    }
  }
}

AppStorage:应用全局的UI状态存储

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

持久化数据PersistentStorage和环境变量Environment都是通过和AppStorage中转,才可以和UI交互。

  • 使用

初始化

AppStorage.SetOrCreate('globalAppStorage', 888);

通过装饰器获取和设置

// 获取
@StorageProp('LocalStorage') varA: number = 0
@StorageLink('LocalStorage') varB: number = 1

// 设置
this.varA += 1

通过AppStorage获取和设置

// 获取
AppStorage.Get<number>('AppStorage')
// 设置
AppStorage.Set<number>('AppStorage', 333)
  • 限制

  • @StorageProp单向数据同步,@StorageLink双向数据同步

  • 应用退出打开,AppStorage数据都没了

  • AppStorage与PersistentStorage以及Environment配合使用时,需要注意:

    • 在AppStorage中创建属性后,调用PersistentStorage.persistProp()接口时,会使用在AppStorage中已经存在的值,并覆盖PersistentStorage中的同名属性,所以建议要使用相反的调用顺序
    • 如果在AppStorage中已经创建属性后,再调用Environment.envProp()创建同名的属性,会调用失败。因为AppStorage已经有同名属性,Environment环境变量不会再写入AppStorage中,所以建议AppStorage中属性不要使用Environment预置环境变量名
    • 状态装饰器装饰的变量,改变会引起UI的渲染更新,如果改变的变量不是用于UI更新,只是用于消息传递,推荐使用 emitter方式
  • Demo

EntryAbility.ts

export default class EntryAbility extends UIAbility {
  onWindowStageCreate(windowStage: window.WindowStage) {
    AppStorage.SetOrCreate('globalAppStorage', 888);
  }
}

AppStoragePage.ets

/*
 * AppStorage:应用全局的UI状态存储
 * 注意:
 * 1.@StorageProp单向数据同步,@StorageLink双向数据同步
 * */
import router from '@ohos.router'

@Entry
@Component
struct AppStoragePage {
  // 先定义的初始值 覆盖后定义的
  @StorageProp('AppStorage') varA: number = 1
  @StorageLink('AppStorage') varB: number = 2

  build() {
    Row() {
      Column() {
        Text(`AppStorage:${this.varA}`).fontSize(26).fontColor(0x0033aa)
        Button('通过AppStorage修改变量').margin(10)
          .onClick(() => {
            AppStorage.Set<number>('AppStorage', AppStorage.Get<number>('AppStorage') + 1)
          })
        Button('直接修改@StorageProp修饰变量').margin(10)
          .onClick(() => {
            this.varA += 1
          })
        Button('直接修改@StorageLink修饰变量').margin(10)
          .onClick(() => {
            this.varB += 1
          })
        Divider()
        Button('跳转AppStorage_Test').margin(10)
          .onClick(() => {
            router.pushUrl({ url: 'pages/AppStoragePage_Test' })
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

AppStoragePage_Test.ets

let storage3 = LocalStorage.GetShared()

@Entry(storage3)
@Component
struct AppStoragePage_Test {
  // globalAppStorage
  @StorageLink('globalAppStorage') appLink: number = 1;
  @LocalStorageLink('globalLocalStorage') varA: number = 1;

  build() {
    Row() {
      Column() {
        // 应用退出打开,AppStorage和LocalStorage数据都没了
        Text(`AppStorage:${this.appLink}`).fontSize(25)
          .onClick(() => {
            this.appLink++
          })
        Text(`LocalStorage:${this.varA}`).fontSize(25)
          .onClick(() => {
            this.varA++
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

PersistentStorage:持久化存储UI状态(目前有自动转string类型的BUG)

PersistentStorage将选定的AppStorage属性保留在设备磁盘上。应用程序通过API,以决定哪些AppStorage属性应借助PersistentStorage持久化。UI和业务逻辑不直接访问PersistentStorage中的属性,所有属性访问都是对AppStorage的访问,AppStorage中的更改会自动同步到PersistentStorage。

PersistentStorage和AppStorage中的属性建立双向同步。应用开发通常通过AppStorage访问PersistentStorage,另外还有一些接口可以用于管理持久化属性,但是业务逻辑始终是通过AppStorage获取和设置属性的。

  • 使用

初始化

PersistentStorage.PersistProp('persistentStorage', 33);

批量初始化

PersistentStorage.PersistProps([{ key: 'highScore', defaultValue: '0' }, { key: 'wightScore', defaultValue: '1' }]);

获取所有key的数组

let keys: Array<string> = PersistentStorage.Keys();

删除

PersistentStorage.DeleteProp('persistentStorage');

通过装饰器获取和设置

// 获取
@StorageLink('persistentStorage') persistentStorage: number = 0

// 设置
this.persistentStorage += 1;

通过AppStorage获取

AppStorage.Get('persistentStorage')
  • 限制

  • number, string, boolean, enum 等简单类型

  • 可以被JSON.stringify()和JSON.parse()重构的对象。例如Date, Map, Set等内置类型则不支持,以及对象的属性方法不支持持久化

  • 不支持嵌套对象(对象数组,对象的属性是对象等)。因为目前框架无法检测AppStorage中嵌套对象(包括数组)值的变化,所以无法写回到PersistentStorage中

  • 避免持久化经常变化的变量

  • PersistentStorage的持久化变量最好是小于2kb的数据,不要大量的数据持久化,因为PersistentStorage写入磁盘的操作是同步的,大量的数据本地化读写会同步在UI线程中执行,影响UI渲染性能(建议使用数据库api)

  • PersistentStorage.PersistProp要放在全局,AppStorage会自动管理

  • PersistentStorage只能在UI页面内使用,否则将无法持久化数据

  • 将key对应的属性从PersistentStorage删除,后续AppStorage的操作,对PersistentStorage不会再有影响

  • 应用退出打开,PersistentStorage数据都在

  • Demo

/*
 * PersistentStorage:持久化存储UI状态
 * PS:LocalStorage和AppStorage都是运行时的内存,但是在应用退出再次启动后,数据无。PersistentStorage数据有。
 * 注意:
 * 1.PersistentStorage.PersistProp 要放在全局,AppStorage会自动管理
 * 2.PersistentStorage只能在UI页面内使用,否则将无法持久化数据
 * 3.将key对应的属性从PersistentStorage删除,后续AppStorage的操作,对PersistentStorage不会再有影响
 * */

// 初始化
PersistentStorage.PersistProp("persistentStorage", 0)

@Entry
@Component
struct PersistentStoragePage {
  @StorageLink('persistentStorage') persistentStorage: number = 0

  // aboutToAppear() {
  //   console.log('Tag', typeof this.persistentStorage)
  // }

  build() {
    Row() {
      Column() {
        Text(`PersistentStorage:${this.persistentStorage}`).fontSize(25)
          .onClick(() => {
            this.persistentStorage += 1;
            PersistentStorage.PersistProp("persistentStorage", this.persistentStorage)
            AppStorage.Set('persistentStorage', this.persistentStorage)
          })
        Button('删除PersistentStorage')
          .onClick(() => {
            PersistentStorage.DeleteProp('persistentStorage')
            AppStorage.Link('persistentStorage').set(null)
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

Environment:设备环境查询

Environment是ArkUI框架在应用程序启动创建的单例对象。应用程序运行的设备的环境参数,以此来作出不同的场景判断,比如多语言,暗黑模式等.

  • 使用

初始化

// 将设备的语言code存入AppStorage,默认值为en
Environment.EnvProp('environmentLang', 'en');

通过装饰器获取

@StorageProp('environmentLang') lang : string = 'en';
  • 限制

  • Environment的所有属性都是不可变的(即应用不可写入)

  • 所有的属性都是简单类型

  • Demo

/*
 * Environment:设备环境查询
 * 注意:
 * 1.Environment的所有属性都是不可变的(即应用不可写入)
 * 2.@StorageProp关联的环境参数可以在本地更改,但不能同步回AppStorage中,因为应用对环境变量参数是不可写的,只能在Environment中查询。
 * */
// 将设备languageCode存入AppStorage中
Environment.EnvProp('environmentLang', 'en');

@Entry
@Component
struct EnvironmentPage {
  @StorageProp('environmentLang') environmentLang: string = 'zh';

  build() {
    Row() {
      Column() {
        // 输出当前设备的languageCode
        Text(this.environmentLang).fontSize(25)
      }
    }
  }
}

其他状态管理

@Watch装饰器:状态变量更改通知

@Watch应用于对状态变量的监听。@Watch在ArkUI框架内部判断数值有无更新使用的是严格相等(===),遵循严格相等规范。当在严格相等为false的情况下,就会触发@Watch的回调。

  • 使用
@State @Watch('change') count: number = 1
  • 限制

  • Watch函数中不要改变被监视的状态变量,会无限循环(弹窗报错)

  • 不建议在@Watch函数中调用async await,因为@Watch设计的用途是为了快速的计算,异步行为可能会导致重新渲染速度的性能问题

  • Demo

/*
 * @Watch装饰器:状态变量更改通知
 * 注意:
 * 1.Watch函数中不要改变被监视的状态变量,会无限循环(P14 弹窗报错)
 * 2.不建议在@Watch函数中调用async await,因为@Watch设计的用途是为了快速的计算,异步行为可能会导致重新渲染速度的性能问题
 * */
@Entry
@Component
struct WatchPage {
  @State @Watch('change') count: number = 1
  @State @Watch('change') pow: number = 2
  @State result: number = 1

  change() {
    // this.count += 2 //无线循环 报错
    this.result = Math.pow(this.count, this.pow)
  }

  build() {
    Row() {
      Column() {
        Text('基数:' + this.count)
          .fontSize(50)
          .onClick(() => {
            this.count++
          })

        Divider()
        Text('次幂:' + this.pow)
          .fontSize(50)
          .onClick(() => {
            this.pow++
          })

        Divider()
        Text('结果:' + this.result)
          .fontSize(50)
      }
      .width('100%')
    }
    .height('100%')
  }
}

$$语法:内置组件双向同步

$$运算符为系统内置组件提供TS变量的引用,使得TS变量和系统内置组件的内部状态保持同步

内部状态具体指什么取决于组件。

  • 使用
@State isRefreshing: boolean = false

Refresh({ refreshing: $$this.isRefreshing, offset: 120, friction: 100 }) {}
  • 限制

  • 当前$$支持基础类型变量,以及@State、@Link和@Prop装饰的变量

  • 当前$$仅支持Refresh组件的refreshing参数

  • $$绑定的变量变化时,会触发UI的同步刷新

  • Demo

/*
 * $$语法:内置组件双向同步
 * 注意:
 * 1.当前$$仅支持Refresh组件的refreshing参数
 * 2.$$绑定的变量变化时,会触发UI的同步刷新
 * */
// 以Refresh组件的refreshing参数为例
@Entry
@Component
struct OtherPage {
  @State isRefreshing: boolean = false
  @State counter: number = 0

  build() {
    Column() {
      Text('isRefreshing: ' + this.isRefreshing)
        .fontSize(30)
        .margin(10)

      // 使用$$语法
      Refresh({ refreshing: $$this.isRefreshing, offset: 120, friction: 100 }) {
        Text('刷新次数: ' + this.counter)
          .fontSize(30)
          .margin(10)
      }
      // 当前刷新状态变更时,触发回调
      .onStateChange((refreshStatus: RefreshStatus) => {
        console.info('Refresh onStatueChange state is ' + refreshStatus)
      })
      // 进入刷新状态时触发回调
      .onRefreshing(() => {
        setTimeout(() => {
          this.counter++
          this.isRefreshing = false
        }, 2000)
      })
    }
  }
}