Harmoney OS学习笔记——管理应用拥有的状态

概述

如果开发者要实现应用级的,或者多个页面的状态数据共享,就需要用到应用级别的状态管理的概念。ArkTS根据不同特性,提供了多种应用状态管理的能力:

  • LocalStorage:页面级UI状态存储,通常用于UIAbility内、页面间的状态共享。
  • AppStorage:特殊的单例LocalStorage对象,由UI框架在应用程序启动时创建,为应用程序UI状态属性提供中央存储。
  • PersistentStorage:持久化存储UI状态,通常和AppStorage配合使用,选择AppStorage存储的数据写入磁盘,以确保这些属性在应用程序重新启动时的值与应用程序关闭时的值相同。
  • Environment:应用程序运行的设备的环境参数,环境参数会同步到AppStorage中可以和AppStorage搭配使用。

LocalStorage:页面级UI状态存储

LocalStorage是页面级的UI状态存储,通过@Entry装饰器接收的参数可以在页面内共享同一个LocalStorage实例。
本文仅介绍LocalStorage使用场景和相关的装饰器:@LocalStorageProp和@LocalStorageLink。

概述

LocalStorage是ArkTS为构建页面级别状态变量提供存储的内存内"数据库"。

  • 应用程序可以创建多个LocalStorage实例,LocalStorage实例可以在页面内共享,也可以通过GetShared接口,实现跨页面、UIAbility实例内共享。
  • 组件树的根节点、即被@Entry装饰的@Component,可以被分配一个LocalStorage实例,此组件的所有子组件实例将自动获得对该LocalStorage实例的访问权限。
  • @Component装饰的组件最多可以访问一个LoaclStorage实例和AppStorage,未被@Entry装饰的组件不可被独立分配LocalStorage实例,只能接收父组件通过@Entry传递来的LoaclStorage实例。一个LocalStorage实例在组件树上可以被分配给多个组件。
  • LocalStorage中的所有属性都是可变的。

应用程序决定LoaclStorage对象的生命周期。当应用释放最后一个指向LocalStorage的引用时,比如销毁最后一个自定义组件,LocalStorage将被JS Engine垃圾回收。
LocalStorage根据与@Component装饰的组件的同步类型不同,提供了两个装饰器:

  • @LocalStorageProp@LocalStroageProp装饰的变量与LocalStorage中给定属性建立单向同步关系。
  • @LocalStorageLink@LocalStorageLink装饰的变量与LocalStorage中给定属性建立双向同步关系。

限制条件

  • LocalStorage创建后,命名属性的类型不可更改。后续调用Set时必须使用相同类型的值。
  • LocalStorage是页面级存储,getShared接口仅能获取当前Stage通过windowStage.loadContent传入的LocalStorage实例,否则返回undefined。

@LocalStorageProp

在上文中已经提到,如果要建立LocalStorage和自定义组件的联系,需要使用@LocalStorageProp和@LocalStorageLink装饰器。
使用@LocalStorageProp(key)/@LocalStorageLink(key)装饰组件内的变量,key标识了LocalStorage的属性。
当自定义组件初始化的时候,@LocalStorageProp(key)/@LocalStorageLink(key)装饰的变量会通过给定的key,绑定LocalStorage对应的属性,完成初始化。
本地初始化是必要的,因为无法保证LocalStorage一定存在给定的key(这取决于应用逻辑是否在组件初始化之前在LocalStorage实例中存入对应的属性)。

@LocalStorageProp(key)是和LocalStorage中key对应的属性建立单向数据同步,ArkUI框架支持修改@LocalStorageProp(key)在本地的值,但是对本地值的修改不会同步回LocalStorage中。
相反,如果LocalStorage中key对应的属性值发生改变,例如通过set接口对LocalStorage中的值进行修改,改变会同步给@LocalStorageProp(key),并覆盖掉本地的值。

装饰器使用规则说明

  • 装饰器参数:key,常量字符串,必填
  • 允许装饰的变量类型
    • Object、class、string、number、boolean、enum类型,以及这些类型的数组。
    • API12及以上支持Map、Set、Date类型。
    • API12及以上支持上述支持类型的联合类型
    • 类型必须被指定,建议和LocalStorage中对应属性类型相同,否则会发生类型隐式转换,从而导致应用行为异常。
    • 不支持any,API12及以上支持undefined和null类型。
  • 同步类型为单向同步,从LocalStorage的对应属性到组件的状态变量。组件本地的修改是允许的,但是LocalStorage给定的属性一旦发生变化,将覆盖本地的修改
  • 必须指定被装饰变量的初始值:如果LocalStorage实例中不存在属性,则用该初始值初始化该属性,并存入LocalStorage中。

变量的传递/访问规则说明

  • 禁止从父节点初始化和更新:@LocalStorageProp不支持从父节点初始化,只能从LocalStorage中key对应的属性初始化,如果没有对应key的话,将使用本地默认初始化。
  • 支持初始化子节点:可用于初始化@State、@Link、@Prop、@Provide
  • 不支持组件外访问

图1 @LocalStorageProp初始化规则图示

观察变化和行为表现

观察变化

  • 当装饰的数据类型为boolean、string、number类型时,可以观察到数值的变化。
  • 当装饰的数据类型为class或者Object时,可以观察到对象整体赋值和对象属性变化(详见从ui内部使用localstorage)。
  • 当装饰的对象是array时,可以观察到数组添加、删除、更新数组单元的变化。
  • 当装饰的对象是Date时,可以观察到Date整体的赋值,同时可通过调用Date的接口setFullYear, setMonth, setDate, setHours, setMinutes, setSeconds, setMilliseconds, setTime, setUTCFullYear, setUTCMonth, setUTCDate, setUTCHours, setUTCMinutes, setUTCSeconds, setUTCMilliseconds 更新Date的属性。详见装饰Date类型变量
  • 当装饰的变量是Map时,可以观察到Map整体的赋值,同时可通过调用Map的接口set, clear, delete 更新Map的值。详见装饰Map类型变量
  • 当装饰的变量是Set时,可以观察到Set整体的赋值,同时可通过调用Set的接口add, clear, delete 更新Set的值。详见装饰Set类型变量

框架行为

  • 被@LocalStorageProp装饰的变量的值的变化不会同步回LocalStorage里。
  • @LocalStorageProp装饰的变量变化会使当前自定义组件中关联的组件刷新。
  • LocalStorage(key)中值的变化会引发所有被@LocalStorageProp对应key装饰的变量的变化,会覆盖@LocalStorageProp本地的改变。

@LocalStorageLink

说明
从API version 11开始,该装饰器支持在元服务中使用。
如果我们需要将自定义组件的状态变量的更新同步回LocalStorage,就需要用到@LocalStorageLink。
@LocalStorageLink(key)是和LocalStorage中key对应的属性建立双向数据同步:

  1. 本地修改发生,该修改会被写回LocalStorage中;
  2. LocalStorage中的修改发生后,该修改会被同步到所有绑定LocalStorage对应key的属性上,包括单向变量(@LocalStorageProp和通过prop创建的单向绑定变量)、双向变量(@LocalStorageLink和通过link创建的双向绑定变量)。

装饰器使用规则说明

  • 装饰器参数:key:常量字符串,必填(字符串需要有引号)。
  • 允许装饰的变量类型
    • Object、class、string、number、boolean、enum类型,以及这些类型的数组。
    • API12及以上支持Map、Set、Date类型。
    • API12及以上支持上述支持类型的联合类型
    • 类型必须被指定,建议和LocalStorage中对应属性类型相同,否则会发生类型隐式转换,从而导致应用行为异常。
    • 不支持any,API12及以上支持undefined和null类型。
  • 同步类型:双向同步,从LocalStorage的对应属性到自定义组件,从自定义组件到LocalStorage对应属性。
  • 被装饰变量的初始值:必须指定,如果LocalStorage实例中不存在属性,则用该初始值初始化该属性,并存入LocalStorage中。

变量的传递/访问规则说明

  • 禁止从父节点初始化和更新:@LocalStorageLink不支持从父节点初始化,只能从LocalStorage中key对应的属性初始化,如果没有对应key的话,将使用本地默认值初始化。
  • 支持初始化子节点,可用于初始化@Stage、@Link、@Prop、@Provide
  • 不支持组件外访问

图2 @LocalStorageLink初始化规则图示

观察变化和行为表现

观察变化

  • 当装饰的数据类型为boolean、string、number类型时,可以观察到数值的变化。
  • 当装饰的数据类型为class或者Object时,可以观察到对象整体赋值和对象属性变化(详见从ui内部使用localstorage)。
  • 当装饰的对象是array时,可以观察到数组添加、删除、更新数组单元的变化。
  • 当装饰的对象是Date时,可以观察到Date整体的赋值,同时可通过调用Date的接口setFullYear, setMonth, setDate, setHours, setMinutes, setSeconds, setMilliseconds, setTime, setUTCFullYear, setUTCMonth, setUTCDate, setUTCHours, setUTCMinutes, setUTCSeconds, setUTCMilliseconds 更新Date的属性。详见装饰Date类型变量
  • 当装饰的变量是Map时,可以观察到Map整体的赋值,同时可通过调用Map的接口set, clear, delete 更新Map的值。详见装饰Map类型变量
  • 当装饰的变量是Set时,可以观察到Set整体的赋值,同时可通过调用Set的接口add, clear, delete 更新Set的值。详见装饰Set类型变量

框架行为

  1. 当@LocalStorageLink(key)装饰的数值改变被观察到时,修改将被同步回LocalStorage对应属性键值key的属性中。
  2. LocalStorage中属性键值key对应的数据一旦改变,属性键值key绑定的所有的数据(包括双向@LocalStorageLink和单向@LocalStorageProp)都将同步修改。
  3. 当@LocalStorageLink(key)装饰的数据本身是状态变量,它的改变不仅仅会同步回LocalStorage中,还会引起所属的自定义组件的重新渲染。

使用场景

应用逻辑使用LocalStorage

let para: Record<string,number> = {'PropA': 47};
// 创建新实例并使用给定对象初始化
let storage: LoaclStroage = new LocalStorage(para);
// propA == 47
let propA: number | undefined = storage.get('PropA')
// link1.get() == 47
let link1: SubscribedAbstractProperty<number> = storage.link('PropA');
// link2.get() == 47
let link2: SubscribedAbstractProperty<number> = storage.link('PropA');
// prop.get() == 47
let prop: SubscribedAbstractProperty<number> = storage.prop('PropA');

// two-way sync: link1.get() == link2.get() == prop.get() == 48
link1.set(48);
// one-way sync: prop.get() == 1; but link1.get() == link2.get() == 48
prop.set(1);
// two-way sync: link1.get() == link2.get() == prop.get() == 49
link1.set(49);

从UI内部使用LoaclStorage

除了应用程序逻辑使用LocalStorage,还可以借助LocalStorage相关的两个装饰器@LocalStorageProp@LocalStorageLink,在UI组件内部获取到LoaclStorage实例厚葬存储的状态变量。
本示例以@LocalStorageLink为例,展示了:

  • 使用构造函数创建LocalStorage实例storage;
  • 使用@Entry装饰器将storage添加到CompA顶层组件中;
  • @LocalStroageLink绑定LocalStorage对给定属性,建立双向数据同步。
class PropB {
  code: number;

  constructor(code: number) {
    this.code = code;
  }
}
// 创建新实例并使用给定对象初始化
let para: Recode<string,number> = {'PropA': 47};
let storage: LoaclStorage = new LoaclStorage(para);
storage.setOrCreate('PropB', new PropB(50));

@Component
struct Child {

  // @LocalStorageLink变量装饰器与LoaclStorage中的'PropA'属性建立双向绑定
  @LocalStorageLink('PropA') childLinkNumber: number = 1;
  // @LocalStorageLink变量装饰器与LocalStorage中的'PropB'属性建立双向绑定
  @LocalStorageLink('PropB') childLinkObject: PropB = new PropB(0);

  build() {
    Column() {
      // 更改将同步至LocalStorage中的'PropA'以及Parent.parentLinkNumber
      Button(`Child from LoaclStorage ${this.childLinkNumber}`)
        .onClick(() => {
          this.childLinkNumber += 1;
        })
    //更改将同步至LocalStorage中的'PropB'以及Parent.parentLinkObject.code
      Button(`Child from LocalStorage ${this.childLinkObject.code}`)
        .onClick(() => {
          this.childLinkObject.code += 1;
        })
    }
  }
}

// 使LocalStorage可从@Component组件中访问
@Entry(storage)
@Component
struct CompA {
  // @LocalStorageLink变量装饰器与LocalStorage中的'PropA'属性建立双向绑定
  @LocalStorageLink('PropA') parentLinNumber: number = 1;
  // @LocalStorageLink变量装饰器与LocalStorage中的'PropB'属性建立双向绑定
  @LocalStorageLink('PropB') parentLinkObject: PropB = new PropB(0);

  build() {
   Column({ space: 15 }) {
     // initial value from LocalStorage will be 47, because 'PropA' initialized already
     Button(`Parent from LocalStorage ${this.parentLinkNumber}`) 
       .onClick(() => {
         this.parentLinkNumber += 1;
       })
// initial value from LocalStorage will be 50, because 'PropB' initialized already
     Button(`Parent from LocalStorage ${this.parentLinkObject.code}`) 
       .onClick(() => {
         this.parentLinkObject.code += 1;
       })
     // @Component子组件自动获得对CompA LocalStorage实例的访问权限。
     Child()
   }
 }
}

@LocalStorageProp和LocalStorage单向同步的简单场景

在下面的的示例中,CompA组件和Child组件分别在本地创建了与storage的’PropA’对应属性的单向同步的数据,我们可以看到:

  • CompA中对this.storageProp1的修改1,只会对CompA生效,并没有同步回storage;
  • Child组件中,Text绑定的storageProp2依旧显示47。
// 创建新实例并使用给定对象初始化
let para: Record<string, number> = {'PropA': 47 };
let storage: LocalStorage = new LocalStorage(para);

// 使LocalStorage可从@Component组件访问
@Entry(storage)
@Component
struct CompA {
  // @LocalStorageProp变量装饰器与LocalStorage中的'PropA'属性建立单向绑定
  @LocalStorageProp('PropA') storageProp1: number = 1;

  build() {
    Column({ space: 15 }) {
      // 点击后从47开始加1,只改变当前组件显示的storageProp1,不会同步到LocalStorage中
      Button(`Parent from LocalStorage ${this.storageProp1}`)
        .onClick(() => {
          this.storageProp1 += 1
        })
      Child()
    }
  }
}

@Component
struct Child {
  // @LocalStorageProp变量装饰器与LocalStorage中的'PropA'属性建立单向绑定
  @LocalStorageProp('PropA') storageProp2: number = 2;

  build() {
    Column({ space: 15 }) {
      // 当CompA改变时,当前storageProp2不会改变,显示47
      Text(`Parent from LocalStorage ${this.storageProp2}`)
    }
  }
}

@LocalStorageLink和LocalStorage双向同步的简单场景

// 构造LocalStorage实例
let para: Record<string, number> = { 'PropA': 47 };
let storage: LocalStorage = new LocalStorage(para);
// 调用link(api9以上)接口构造'PropA'的双向同步数据,linkToPropA 是全局变量
let linkToPropA: SubscribedAbstractProperty<object> = storage.link('PropA');

@Entry(storage)
@Component
struct CompA {

  // @LocalStorageLink('PropA')在CompA自定义组件中创建'PropA'的双向同步数据,初始值为47,因为在构造LocalStorage已经给“PropA”设置47
  @LocalStorageLink('PropA') storageLink: number = 1;

  build() {
    Column() {
      Text(`incr @LocalStorageLink variable`)
        // 点击“incr @LocalStorageLink variable”,this.storageLink加1,改变同步回storage,全局变量linkToPropA也会同步改变

        .onClick(() => {
          this.storageLink += 1
        })

      // 并不建议在组件内使用全局变量linkToPropA.get(),因为可能会有生命周期不同引起的错误。
      Text(`@LocalStorageLink: ${this.storageLink} - linkToPropA: ${linkToPropA.get()}`)
    }
  }
}

兄弟组件之间同步状态变量

let ls: Record<string, nubmer> = {'countStorage': 1}
let stroage: LocalStorage = new LocalStorage(ls);

@Component
struct Child {
  label: string = 'no name';
  @LocalStorageLink('countStorage') playCountLink: number = 0;

  build() {
    Row() {
      Text(this.label)
        .width(50).height(60).fontSize(12)
      Text(`playCountLink ${this.playCountLink}: inc by 1`)
        .onClick(() => {
          this.playCountLink += 1;
        })
        .width(200).height(60).fontSize(12)
    }.width(300).height(60)
  }
}

@Entry(storage)
@Component
struct Parent {
  @LocalStorageLink('countStorage') playCount: number = 0;

  build() {
    Column() {
      Row() {
        Text('Parent')
          .width(50).height(60).fontSize(12)
        Text(`playCount ${this.playCount} dec by 1`)
          .onClick(() => {
            this.playCount -= 1;
          })
          .width(250).height(60).fontSize(12)
      }.width(300).height(60)

      Row() {
        Text('LocalStorage')
         .width(50).height(60).fontSize(12)
        Text(`countStorage ${this.playCount} incr by 1`)
          .onClick(() => {
            storage.set<number | undefined>('countStorage', Number(storage.get<number>('countStorage')) + 1);
          })
        .width(300).height(60)

        Child({ label: 'ChildA'})
        Child({ label: 'ChildB' })

        Text(`playCount in LocalStorage for debug ${storage.get<number>('countStorage')}`)
          .width(300).height(60).fontSize(12)
      }
    }
  }
}

将LocalStorage实例从UIAbility共享到一个或多个视图

上面的实例中,LoaclStorage的实例仅仅在一个@Entry装饰的组件和其所属的子组件(一个页面)中共享,如果其在多个视图中共享,可以在所属UIAbility中创建LocalStorage实例,并调用windowStage.loadContent

import { UIAbility } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';

export default class EntryAbility extends UIAbility {
  para: Record<string, number> = {'PropA': 47};
  storage: LocalStorage = new LoaclStorage(this.para);

  onWindowStageCreate(windowStage: window.WindowStage) {
    windowStage.loadContent('pages/Index', this.storage);
  }
}

在下面的用例中,Index页面中的propA通过getShared()方法获取到共享的LocalStorage实例。点击Button跳转到Page页面,点击Change propA改变propA的值,back回Index页面后,页面中propA的值也同步修改。

import { router } from '@kit.ArkUI';

let storage = LocalStorage.getShared()

@Entry(storage)
@Component
struct Index {

  @LocalStorageLink('PropA') propA: nubmer = 1;

  build() {
    Row() {
      Column() {
        Text(`${this.propA}`)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
        Button("To Page")
          .onClick(() => {
            router.pushUrl({
              url: 'pages/Page'
            })
          })
      }.width('100%')
    }.height('100%')
  }
}
import { router } from '@kit.ArkUI';

let storage = LocalStorage.getShared()

@Entry(stroage)
@Component
struct Page {
  @LocalStroageLink('PropA') propA: number = 2;

  build() {
    Row() {
      Column() {
        Text(`${this.propA}`)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
        Button("Change propA")
          .onClick(() => {
            router.back()
          })
      }
    }.width('100%')
  }
}

自定义组件接收LocalStorage实例

除了根节点可通过@Entry来接收LocalStorage实例2,自定义组件(子节点)也可以通过构造参数来传递LocalStorage实例。
本示例以@LocalStorageLink为例,展示了:

  • 父组件中的Text,显示LocalStorage实例localStorage1中PropA的值为"PropA"。
  • Child组件中,Text绑定的PropB,显示LocalStorage实例localStorage2PropB的值为"PropB"
    :::success
    说明
    从API version 12开始,自定义组件支持接收LocalStorage实例。
    当自定义组件作为子节点,定义了成员属性时,LocalStorage实例必须要放在第二个参数位置传递,否则会报类型不匹配的编译问题。
    当在自定义组件中定义了属性时,暂时不支持只有一个LocalStorage实例作为入参。如果没定义属性,可以只传入一个LocalStorage实例作为入参
    如果定义的属性不需要从父组件初始化变量,则第一个参数需要传{}
    作为构造参数传给子组件的LocalStorage实例在初始化时就会被决定,可以通过@LocalStorageLink或者LocalStorage的API修改LocalStorage实例中保存的属性值,但LocalStorage实例自身不能被动态修改。
    :::
let localStorage1: LocalStorage = new LocalStorage();
localStorage1.setOrCreate('PropA','PropA');

let localStorage2: LocalStorage = new LocalStorage();
localStorage2.setOrCreate('PropB','PropB');

@Entry(localStorage1)
@Component
struct Index {
  @LocalStorageLink('PropA') propA: string ='Hello World';
  @State count: number = 0;

  build() {
    Row() {
      Column() {
        Text(this.propA)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
        Child({ count: this.count }, localStorage2)
      }
      .width('100%')
    }
    .height('100%')
  }
}

@Component
struct Child {
  @Link count: number;
  // 'Hello World',和localStorage2中'propB'的双向同步,localStorage2中没有'propB', 则使用默认值'Hello World'
  @LocalStorageLink('PropB') propB: string = 'Hello World';

  build() {
    Text(this.propB)
      .fontSize(50)
      .fontWeight(FontWeight.Bold)
  }
}

当定义的属性不需要从父组件初始化变量时,第一个参数需要传{}

let localStorage1: LocalStorage = new LocalStorage();
localStorage1.setOrCreate('PropA', 'PropA');

let localStorage2: LocalStorage = new LocalStorage();
localStorage2.setOrCreate('PropB', 'PropB');

@Entry(localStorage1)
@Component
struct Index {
  // 'PropA',和localStorage1中'PropA'的双向同步
  @LocalStorageLink('PropA') PropA: string = 'Hello World';
  @State count: number = 0;

  build() {
    Row() {
      Column() {
        Text(this.PropA)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
        // 使用LocalStorage 实例localStorage2
        Child({}, localStorage2)
      }
      .width('100%')
    }
    .height('100%')
  }
}



@Component
struct Child {
  @State count: number = 5;
  // 'Hello World',和localStorage2中'PropB'的双向同步,localStorage2中没有'PropB',则使用默认值'Hello World'
  @LocalStorageLink('PropB') PropB: string = 'Hello World';

  build() {
    Text(this.PropB)
      .fontSize(50)
      .fontWeight(FontWeight.Bold)
  }
}

Navigation组件和LocalStorage联合使用

可以通过传递不同的LocalStorage实例给自定义组件,从而实现在navigation跳转到不同页面时,绑定不同的LocalStorage实例,显示对应绑定的值。

let localStorageA: LocalStorage = new LocalStorage();
localStorageA.setOrCreate('PropA', 'PropA');

let localStorageB: LocalStorage = new LocalStorage();
localStorageB.setOrCreate('PropB', 'PropB');

let localStorageC: LocalStorage = new LocalStorage();
localStorageC.setOrCreate('PropC', 'PropC');

@Entry
@Component
struct MyNavigationTestStack {
  @Provide('pageInfo') pageInfo: NavPathStack = new NavPathStack();

  @Builder
  PageMap(name: string) {
    if (name === 'pageOne') {
      // 传递不同的LocalStorage实例
      pageOneStack({}, localStorageA)
    } else if (name === 'pageTwo') {
      pageTwoStack({}, localStorageB)
    } else if (name === 'pageThree') {
      pageThreeStack({}, localStorageC)
    }
  }

  build() {
    Column({ space: 5 }) {
      Navigation(this.pageInfo) {
        Column() {
          Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
            .width('80%')
            .height(40)
            .margin(20)
            .onClick(() => {
              this.pageInfo.pushPath({ name: 'pageOne' }); //将name指定的NavDestination页面信息入栈
            })
        }
      }.title('NavIndex')
      .navDestination(this.PageMap)
      .mode(NavigationMode.Stack)
      .borderWidth(1)
    }
  }
}

@Component
struct pageOneStack {
  @Consume('pageInfo') pageInfo: NavPathStack;
  @LocalStorageLink('PropA') PropA: string = 'Hello World';

  build() {
    NavDestination() {
      Column() {
        NavigationContentMsgStack()
        // 显示绑定的LocalStorage中PropA的值'PropA'
        Text(`${this.PropA}`)
        Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
          .width('80%')
          .height(40)
          .margin(20)
          .onClick(() => {
            this.pageInfo.pushPathByName('pageTwo', null);
          })
      }.width('100%').height('100%')
    }.title('pageOne')
    .onBackPressed(() => {
      this.pageInfo.pop();
      return true;
    })
  }
}

@Component
struct pageTwoStack {
  @Consume('pageInfo') pageInfo: NavPathStack;
  @LocalStorageLink('PropB') PropB: string = 'Hello World';

  build() {
    NavDestination() {
      Column() {
        NavigationContentMsgStack()
        // 绑定的LocalStorage中没有PropA,显示本地初始化的值 'Hello World'
        Text(`${this.PropB}`)
        Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
          .width('80%')
          .height(40)
          .margin(20)
          .onClick(() => {
            this.pageInfo.pushPathByName('pageThree', null);
          })

      }.width('100%').height('100%')
    }.title('pageTwo')
    .onBackPressed(() => {
      this.pageInfo.pop();
      return true;
    })
  }
}

@Component
struct pageThreeStack {
  @Consume('pageInfo') pageInfo: NavPathStack;
  @LocalStorageLink('PropC') PropC: string = 'pageThreeStack';

  build() {
    NavDestination() {
      Column() {
        NavigationContentMsgStack()

        // 绑定的LocalStorage中没有PropA,显示本地初始化的值 'pageThreeStack'
        Text(`${this.PropC}`)
        Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
          .width('80%')
          .height(40)
          .margin(20)
          .onClick(() => {
            this.pageInfo.pushPathByName('pageOne', null);
          })

      }.width('100%').height('100%')
    }.title('pageThree')
    .onBackPressed(() => {
      this.pageInfo.pop();
      return true;
    })
  }
}

@Component
struct NavigationContentMsgStack {
  @LocalStorageLink('PropA') PropA: string = 'Hello';

  build() {
    Column() {
      Text(`${this.PropA}`)
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
    }
  }
}

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

概述

AppStroage是应用全局的UI状态存储,是和应用的进程绑定的,由UI框架在应用程序启动时创建,为应用程序UI状态属性提供中央存储。
AppStroage不同的是,LocalStorage是页面级的,通常应用于页面内的数据共享。而AppStorage是应用级的全局状态共享,还相当于整个应用的"中枢",持久化数据PersistentStorage环境变量Environment都是通过AppStorage中转,才可以和UI交互。
AppStorage是在应用启动的时候会被创建的单例。它的目的是为了提供应用状态数据的中心存储,这些状态数据在应用级别都是可访问的。AppStorage将在应用运行过程中保留其属性。AppStorage将在应用运行过程保留其属性。属性通过唯一的键字符串值访问。
AppStorage可以和UI组件同步,且可以在应用业务逻辑中被访问。
AppStorage支持应用的主线程内多个UIAbility实例间的状态共享。
AppStorage中的属性可以被双向同步,数据可以是存在于本地或远程设备上,并具有不同的功能,比如数据持久化。这些数据是通过业务逻辑中实现,与UI解耦,如果希望这些数据在UI中使用,需要用到@StorageProp@StorageLink

@StorageProp

在上文中已经提到,如果要建立AppStorage和自定义组件的联系,需要使用@StorageProp@StorageLink装饰器。使用@StorageProp(key)/@StorageLink(key)装饰组件内的变量,key标识了AppStorage的属性。
当自定义组件初始化的时候,会使用AppStorage中对应key的属性值将@StorageProp(key)/@StorageLink(key)装饰的变量初始化。由于应用逻辑的差异,无法确认是否在组件初始化之前向AppStorage实例中存入了对应的属性,所以AppStorage不一定存在key对应的属性,因此@StorageProp(key)/@StorageLink(key)装饰的变量进行本地初始化是必要的。
@StorageProp(key)是和AppStorage中key对应的属性建立单向数据同步,允许本地改变,但是对于@StorageProp,本地的修改永远不会同步回AppStorage中,相反,如果AppStorage给定key的属性发生改变,改变会被同步给@StorageProp,并覆盖掉本地的修改。
说明
从API version 11开始,该装饰器支持在元服务中使用。

装饰器使用规则说明

  • 装饰器参数:key,常量字符串,必填
  • 允许装饰的变量类型
    • Object、class、string、number、boolean、enum类型,以及这些类型的数组。
    • API12及以上支持Map、Set、Date类型。
    • API12及以上支持上述支持类型的联合类型
    • 类型必须被指定,建议和LocalStorage中对应属性类型相同,否则会发生类型隐式转换,从而导致应用行为异常。
    • 不支持any,API12及以上支持undefined和null类型。
  • 同步类型:
    • 单向同步,从AppStorage的对应属性到组件的状态变量。
    • 组件本地的修改是允许的,但是AppStorage中给定的属性一旦发生变化,将覆盖本地的修改

变量的传递/访问规则说明

传递/访问说明
从父节点初始化和更新禁止,@StorageProp不支持从父节点初始化,只能AppStorage中key对应的属性初始化,如果没有对应key的话,将使用本地默认值初始化
初始化子节点支持,可用于初始化@State、@Link、@Prop、@Provide。
是否支持组件外访问否。

图1 @StorageProp初始化规则图示

观察变化和行为表现

观察变化

  • 当装饰的数据类型为boolean、string、number类型时,可以观察到数值的变化。
  • 当装饰的数据类型为class或者Object时,可以观察到对象整体赋值和对象属性变化(详见从ui内部使用appstorage和localstorage)。
  • 当装饰的对象是array时,可以观察到数组添加、删除、更新数组单元的变化。
  • 当装饰的对象是Date时,可以观察到Date整体的赋值,同时可通过调用Date的接口setFullYear, setMonth, setDate, setHours, setMinutes, setSeconds, setMilliseconds, setTime, setUTCFullYear, setUTCMonth, setUTCDate, setUTCHours, setUTCMinutes, setUTCSeconds, setUTCMilliseconds 更新Date的属性。详见装饰Date类型变量
  • 当装饰的变量是Map时,可以观察到Map整体的赋值,同时可通过调用Map的接口set, clear, delete 更新Map的值。详见装饰Map类型变量
  • 当装饰的变量是Set时,可以观察到Set整体的赋值,同时可通过调用Set的接口add, clear, delete 更新Set的值。详见装饰Set类型变量

框架行为

  • 当@StorageProp(key)装饰的数值改变被观察到时,修改不会被同步回AppStorage对应属性键值key的属性中。
  • 当前@StorageProp(key)单向绑定的数据会被修改,即仅限于当前组件的私有成员变量改变,其他的绑定该key的数据不会同步改变。
  • 当@StorageProp(key)装饰的数据本身是状态变量,它的改变虽然不会同步回AppStorage中,但是会引起所属的自定义组件的重新渲染。
  • 当AppStorage中key对应的属性发生改变时,会同步给所有@StorageProp(key)装饰的数据,@StorageProp(key)本地的修改将被覆盖。

@StorageLink

说明
从API version 11开始,该装饰器支持在元服务中使用。
@StorageLink(key)是和AppStorage中key对应的属性建立双向数据同步:

  1. 本地修改发生,该修改会被写回AppStorage中;
  2. AppStorage中的修改发生后,该修改会被同步到所有绑定AppStorage对应key的属性上,包括单向(@StorageProp和通过Prop创建的单向绑定变量)、双向(@StorageLink和通过Link创建的双向绑定变量)变量和其他实例(比如PersistentStorage)。

装饰器使用规则说明

  • 装饰器类型:key,常量字符串,必填
  • 允许装饰的变量类型
    • Object、class、string、number、boolean、enum类型,以及这些类型的数组。
    • API12及以上支持Map、Set、Date类型。
    • API12及以上支持上述支持类型的联合类型
    • 类型必须被指定,建议和LocalStorage中对应属性类型相同,否则会发生类型隐式转换,从而导致应用行为异常。
    • 不支持any,API12及以上支持undefined和null类型。
  • 同步类型为双向同步,从AppStorage的对应属性到自定义组件,从自定义组件到AppStorage对应属性
  • 必须指定被装饰变量的初始值:如果AppStorage实例不存在属性,则用该初始值初始化该属性,并存入AppStorage中。

变量的传递/访问规则说明

  • 禁止从父组件初始化和更新
  • 支持初始化子节点,可用于初始化常规变量、@State、@Link、@Prop、@Provide。
  • 不支持组件外访问

图2 @StorageLink初始化规则图示

观察变化和行为表现

观察变化

  • 当装饰的数据类型为boolean、string、number类型时,可以观察到数值的变化。
  • 当装饰的数据类型为class或者Object时,可以观察到对象整体赋值和对象属性变化(详见从ui内部使用appstorage和localstorage)。
  • 当装饰的对象是array时,可以观察到数组添加、删除、更新数组单元的变化。
  • 当装饰的对象是Date时,可以观察到Date整体的赋值,同时可通过调用Date的接口setFullYear, setMonth, setDate, setHours, setMinutes, setSeconds, setMilliseconds, setTime, setUTCFullYear, setUTCMonth, setUTCDate, setUTCHours, setUTCMinutes, setUTCSeconds, setUTCMilliseconds 更新Date的属性。详见装饰Date类型变量
  • 当装饰的变量是Map时,可以观察到Map整体的赋值,同时可通过调用Map的接口set, clear, delete 更新Map的值。详见装饰Map类型变量
  • 当装饰的变量是Set时,可以观察到Set整体的赋值,同时可通过调用Set的接口add, clear, delete 更新Set的值。详见装饰Set类型变量

框架行为

  1. 当@StorageLink(key)装饰的数值改变被观察到时,修改将被同步回AppStorage对应属性键值key的属性中。
  2. AppStorage中属性键值key对应的数据一旦改变,属性键值key绑定的所有的数据(包括双向@StorageLink和单向@StorageProp)都将同步修改。
  3. 当@StorageLink(key)装饰的数据本身是状态变量,它的改变不仅仅会同步回AppStorage中,还会引起所属的自定义组件的重新渲染。

限制条件

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

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

使用场景

从应用逻辑使用AppStorage和LocalStorage

AppStorage是单例,它的所有API都是静态的,使用方法类似于LocalStorage中对应的非静态方法。

AppStorage.setOrCreate('PropA', 47);

let storage: LocalStorage = new LocalStorage();
storage.setOrCreate('PropA',17);
let propA: number | undefined = AppStorage.get('PropA') // propA in AppStorage == 47, propA in LocalStorage == 17
let link1: SubscribedAbstractProperty<number> = AppStorage.link('PropA'); // link1.get() == 47
let link2: SubscribedAbstractProperty<number> = AppStorage.link('PropA'); // link2.get() == 47
let prop: SubscribedAbstractProperty<number> = AppStorage.prop('PropA'); // prop.get() == 47

link1.set(48); // two-way sync: link1.get() == link2.get() == prop.get() == 48
prop.set(1); // one-way sync: prop.get() == 1; but link1.get() == link2.get() == 48
link1.set(49); // two-way sync: link1.get() == link2.get() == prop.get() == 49

storage.get<number>('PropA') // == 17
storage.set('PropA', 101);
storage.get<number>('PropA') // == 101

AppStorage.get<number>('PropA') // == 49
link1.get() // == 49
link2.get() // == 49
prop.get() // == 49

从UI内部使用AppStorage和LocalStorage

@StorageLink变量装饰器与AppStorage配合使用,正如@LocalStorageLinkLocalStorage配合使用一样。此装饰器使用AppStorage中的属性创建双向数据同步。

class PropB {
  code: number;

  constructor(code: number) {
    this.code = code;
  }
}

AppStorage.setOrCreate('PropA',47);
AppStorage.setOrCreate('PropB', new PropB(50));
let storage = new LocalStorage()
storage.setOrCreate('PropA', 48)
storage.setOrCreate('PropB', new PropB(100))

@Entry(storage)
@Component
struct CompA {
  @StorageLink('PropA') storageLink: number = 1;
  @LocalStorageLink('PropA') localStorageLink: number = 1;
  @StorageLink('PropB') storageLinkObject: PropB = new PropB(1);
  @LocalStorageLink('PropB') localStorageLinkObject: PropB = new PropB(1);

  build() {
    Column({ space: 20 }){
      Text(`From AppStorage ${this.storageLink}`)
        .onClick(() => {
          this.storageLink += 1;
        })
      Text(`From LocalStorage ${this.localStorageLink}`)
        .onClick(() => {
          this.localStorageLink += 1;
        })

      Text(`From AppStorage ${this.storageLinkObject.code}`)
        .onClick(() => {
          this.storageLinkObject.code += 1;
        })

      Text(`From LocalStorage ${this.localStorageLinkObject.code}`)
        .onClick(() => {
          this.localStorageLinkObject.code += 1;
        })
    }
  }
}

不建议借助@StorageLink的双向同步机制实现事件通知

建议使用emitter,使用emit订阅某个事件并接收事件回调的方式来减少开销,增加代码的可读性。

import { emitter } from '@kit.BasicServiceKit';

let NextID: number = 0;

class ViewData {
  title: string
  uri: Resource;
  color: Color = Color.Black;
  id: number;

  constructor(title: string, uri: Resource) {
    this.title = title;
    this.uri = uri;
    this.id = NextID++;
  }
}

@Entry
@Component
struct Gallery2 {
  dataList: Array<ViewData> = [new ViewData('flower', $r('app.media.icon')), new ViewData('OMG', $r('app.media.icon')), new ViewData('OMG', $r('app.media.icon'))]
  scroller: Scroller = new Scroller()

  private preIndex: number = -1

  build() {
    Column() {
      Grid(this.scroller) {
        ForEach(this.dataList, (item: ViewData) => {
          GridItem() {
            TapImage({
              uri: item.uri,
              index: item.id
            })
          }.aspectRatio(1)
          .onClick(() => {
            if( this.preIndex === item.id) {
              return
            }
            let innerEvent: emitter.InnerEvent = { eventId: item.id }
            let eventData: emitter.EventData = {
              data: {
                "colorTag": 1
              }
            }
            emitter.emit(innerEvent, eventData)

            if(this.preIndex != -1) {
              console.info(`preIndex: ${this.preIndex}, index: ${item.id}, black`)
              let eventData: emitter.EventData = {
                data: {
                  "colorTag": 0
                }
              }
              emitter.emit(innerEvent, eventData)
            }
            this.preIndex = item.id
          })
        },(item: ViewData) => JSON.stringify(item))
      }.columnsTemplate('1fr 1fr')
    }
  }
}

@Component
export struct TapImage {
  @State tapColor: Color = Color.Black;
  private index: number = 0;

  private uri: Resource = {
    id: 0,
    type: 0,
    moduleName: "",
    bundleName: ""
  };

  onTapIndexChange(colorTag: emitter.EventData) {
    if( colorTag.data != null){
      this.tapColor = colorTag.data.colorTag ? Color.Red : Color.Black
    }
  }

  aboutToAppear() {
    let innerEvent: emitter.InnerEvent = { eventId: this.index }
    emitter.on(innerEvent, data => {
      this.onTapIndexChange(data)
    })
  }

  build() {
    Column() {
      Image(this.uri)
        .objectFit(ImageFit.Cover)
        .border({ width: 5, style: BorderStyle.Dotted, color: this.tapColor})
    }
  }
}

以上通知事件逻辑简单,也可以简化成三元表达式。

// xxx.ets
class ViewData {
  title: string;
  uri: Resource;
  color: Color = Color.Black;

  constructor(title: string, uri: Resource) {
    this.title = title;
    this.uri = uri
  }
}

@Entry
@Component
struct Gallery2 {
  dataList: Array<ViewData> = [new ViewData('flower', $r('app.media.icon')), new ViewData('OMG', $r('app.media.icon')), new ViewData('OMG', $r('app.media.icon'))]
  scroller: Scroller = new Scroller()

  build() {
    Column() {
      Grid(this.scroller) {
        ForEach(this.dataList, (item: ViewData, index?: number) => {
          GridItem() {
            TapImage({
              uri: item.uri,
              index: index
            })
          }.aspectRatio(1)

        }, (item: ViewData, index?: number) => {
          return JSON.stringify(item) + index;
        })
      }.columnsTemplate('1fr 1fr')
    }

  }
}

@Component
export struct TapImage {
  @StorageLink('tapIndex') tapIndex: number = -1;
  private index: number = 0;
  private uri: Resource = {
    id: 0,
    type: 0,
    moduleName: "",
    bundleName: ""
  };

  build() {
    Column() {
      Image(this.uri)
        .objectFit(ImageFit.Cover)
        .onClick(() => {
          this.tapIndex = this.index;
        })
        .border({
          width: 5,
          style: BorderStyle.Dotted,
          color: (this.tapIndex >= 0 && this.index === this.tapIndex) ? Color.Red : Color.Black
        })
    }
  }
}

PersistentStorage:持久化存储UI状态

概述

LocalStroageAppStorage都是运行时内存,但是在应用退出后再次启动后,依然能保存选定的结果,是应用开发中十分常见的现象,这就需要用到PersistentStroage
PersistentStorage是应用程序中的可选单例对象。此对象的作用是持久化存储选定的AppStorage属性,以确保这些属性在应用程序重新启动时的值与应用程序关闭时的值相同。

PersistentStroage将选定的AppStorage属性保留在设备磁盘上。应用程序通过API,以决定哪些AppStorage的访问,AppStorage中的更改会自动同步到PersistentStorage
PersistentStorage持久化。UI和业务逻辑不直接访问PersistentStorage中的属性,所有属性访问都是对AppStroage的访问,AppStroage中的更改会自动同步到PersistentStorage
PersistentStorageAppStorage中的属性建立双向同步。应用开发通常通过AppStroage访问PersistentStorage,另外还有一些接口可以用于管理持久化属性,但是业务逻辑始终是通过AppStorage获取和设置属性的。

限制条件

PersistentStorage允许的类型和值有:

  • number,string,boolean,enum等简单类型
  • 可以被JSON.stringify()JSON.parse()重构的对象,以及对象的属性方法不支持持久化。
  • API12及以上支持Map类型,可以观察到Map整体的赋值,同时可通过调用Map的接口set, clear, delete 更新Map的值。且更新的值被持久化存储。详见装饰Map类型变量
  • API12及以上支持Set类型,可以观察到Set整体的赋值,同时可通过调用Set的接口add, clear, delete 更新Set的值。且更新的值被持久化存储。详见装饰Set类型变量
  • API12及以上支持Date类型,可以观察到Date整体的赋值,同时可通过调用Date的接口setFullYear, setMonth, setDate, setHours, setMinutes, setSeconds, setMilliseconds, setTime, setUTCFullYear, setUTCMonth, setUTCDate, setUTCHours, setUTCMinutes, setUTCSeconds, setUTCMilliseconds 更新Date的属性。且更新的值被持久化存储。详见装饰Date类型变量
  • API12及以上支持undefined 和 null。
  • API12及以上支持联合类型

PersistentStorage不允许的类型和值有:

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

持久化数据是一个相对缓慢的操作,应用程序应避免以下情况:

  • 持久化大型数据集
  • 持久化经常变化的变量。

PersistentStorage的持久化变量最好是小于2kb的数据,不要大量的数据持久化,因为PersistentStorage写入磁盘的操作是同步的,大量的数据本地化读写会同步在UI线程中执行,影响UI渲染性能。如果开发者需要存储大量的数据,建议使用数据api。
PersistentStorage的持久化变量最好是小于2kb的数据,不要大量的数据持久化,因为PersistentStorage写入磁盘的操作是同步的,大量的数据本地化读写会同步在UI线程中执行,影响UI渲染性能。如果开发者需要存储大量的数据,建议使用数据库api。
PersistentStorage和UI实例相关联,持久化操作需要在UI实例初始化成功后(即loadContent传入的回调被调用时)才可以被调用,早于该时机调用会导致持久化失败。

onWindowStageCreate(windowStage: window.WindowStage): void {
  windowStage.loadContent('pages/Index', (err) => {
    if( err.code) {
      return;
    }
    PersistentStorage.persistProp('aProp', 47);
  })
}

使用场景

从AppStorage中访问PersistentStorage初始化的属性

  1. 初始化PersistentStorage:
PersistentStorage.persistProp('aProp', 47);
  1. 在AppStorage获取对应属性:
AppStroage.get<number>('aProp');

或在组件内部定义:

@StorageLink('aProp') aProp: number = 48;

完整代码如下:

PersistentStorage.persistProp('aProp', 47);

@Entry
@Component
struct Index {
  @State message: string = 'Hello World'
  @StorageLink('aProp') aProp: number = 48

  build() {
    Row() {
      Column() {
        Text(this.message)
        Text(`${this.aProp}`)
          .onClick(() => {
            this.aProp += 1;
          })
      }
    }
  }
}
  • 新应用安装后首次启动运行:
    1. 调用persistProp初始化PersistentStorage,首先查询在PersistentStorage本地文件中是否存在"aProp",查询结果为不存在,因为应用是第一层安装。
    2. 接着查询属性"aProp"在AppStroage中是否存在,依旧不存在。
    3. 在AppStorage中创建名为"aProp"的number类型属性,属性初始值是定义的默认值47。
    4. PersistentStorage将属性"aProp"和值47写入磁盘,AppStorage中"aProp"对应的值和其后续的更改将被持久化。
    5. 在Index组件中创建状态变量@StroageLink('aProp') aProp,和AppStroage中"aProp"双向绑定,在创建的过程中会在AppStorage中查找,成功找到"aProp",所有使用其在AppStroage找到的值47。

  • 触发点击事件后:
    1. 状态变量@StroageLink('aProp') aProp改变,触发Text组件重新刷新。
    2. @StorageLink装饰的变量是和AppStorage中建立双向同步的,所以@StorageLink('aProp') aProp的变化会被同步回AppStroage中。
    3. AppStorage中"aProp"属性的改变会同步到所以绑定该"aProp"的单向或者双向变量,在本示例中没有其他的绑定"aProp"的变量。
    4. 因为"aProp"对应的属性已经被持久化,所以在AppStorage中"aProp"的改变会触发PersistentStorage,将新的改变写入本地磁盘。
  • 后续启动应用:
    1. 执行PersistentStorage.persistentProp('aProp',47),在首先查询在PersistentStorage本地文件查询"aProp"属性,成功查询到。
    2. 将在PersistentStorage查询到的值写入AppStorage中。
    3. 在Index组件里,@StorageLink绑定的"aProp"为PersistentStorage写入AppStroage中的值,即为上一次退出应用存入的值。

在PersistentStorage之前访问AppStorage中的属性

反例。在调用PersistentStorage.persistProp或者persistProp之前使用接口访问AppStorage中的属性是错误的,因为这样的调用顺序会丢失上一次应用程序运行中的属性值

let aProp = AppStorage.setOrCreate('aProp', 47);
PersistentStorage.persistProp('aProp', 48);

应用在非首次运行时,先执行AppStorage.setOrCreate('aProp', 47):属性aProp在AppStorage中创建,其类型为number,其值设置为指定的默认值47。“aProp"是持久化的属性,所以会被写回PersistentStorage磁盘中,PersistentStorage磁盘中,PersistentStorage存储的上次退出应用的值丢失。
PersistentStorage.persistProp('aProp', 48):在PersistentStorage中查找"aProp”,值为刚刚使用AppStorage接口写入的47。

在PersistentStorage之后访问AppStorage中的属性

开发者可以先判断是否需要覆盖上一次保存在PersistentStorage中的值,如果需要覆盖,再调用AppStorage的接口进行修改,如果不需要覆盖,则不调用AppStorage的接口。

PersistentStorage.persistentProp('aProp', 48);
if (AppStorage.get('aProp') > 50) {
  AppStorage.setOrCreate('aProp', 47);
}

示例代码在读取PersistentStorage储存的数据后判断"aProp"的值是否大于50,如果大于50的话使用AppStorage的接口设置为47。

Environment:设备环境变量查询

开发者如果需要应用程序运行的设备的环境参数,以此来作出不同的场景判断
Environment是ArkUI框架在应用程序启动时创建的单例对象。它为AppStorage提供了一系列描述应用程序运行状态的属性。Enviroment的所有属性都是不可变的(即应用不可写入),所有的属性都是简单类型。

Environment内置参数

数据类型描述
accessibilityEnabledboolean获取无障碍屏幕读取
colorModeColorMode色彩模型类型:选项为ColorMode.LIGHT:浅色,ColorMode.DARK:深色。
fontScalenumber字体大小比例,范围:[0.85, 1.45]。
fontWeightScalenumber字体粗细程度,范围:[0.6, 1.6]。
layoutDirectionLayoutDirection布局方向类型:包括LayoutDirection.LTR:从左到右,LayoutDireciton.RTL:从右到左。
languageCodestring当前系统语言值,取值必须为小写字母,例如zh

使用场景

从UI中访问Environment参数

  • 使用Environment.envProp将设备运行的环境变量存入AppStorage中:
Environment.envProp('languageCode','en');
  • 可以使用@StorageProp链接到Component中。
@StorageProp('languageCode') lang: string = 'en';

设备环境到Component的更新链:Environment–>AppStorage–>Component。
@StorageProp关联的环境参数可以在本地更改,但不能同步回AppStorage中,因为应用对环境变量参数是不可写的,只能在Environment中查询。

// 将设备languageCode存入AppStorage中
Evironment.envProp('languageCode','en');

@Entry
@Component
struct Index {
  @StroageProp('languageCode') languageCode: string = 'en';

  build() {
    Row() {
      Column() {
        Text(this.languageCode)
      }
    }
  }
}

应用逻辑使用Environment

// 使用Environment.EnvProp将设备运行languageCode存入AppStorage中;
Environment.envProp('languageCode','en');
// 从AppStorage获取单向绑定的languageCode的变量
const lang: SubscribedAbstractProperty<string> = AppStorage.prop('languageCode');

if( lang.get() === 'zh'){
  console.info('你好');
} else {
  console.info('Hello!');
}

限制条件

EnvironmentUIContext相关联,需要在UIContext明确的时候才可以调用。可以通过在runScopedTask里明确上下文。如果没有在UIContext明确的地方调用,将导致无法查询到设备环境数据。

import { UIAbility } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';

export default class EntryAbility extends UIAbility {
  onWindowStageCreate(windowStage: window.WindowStage) {
    windowStage.loadContent('pages/Index');
    let window = windowStage.getMainWindow()
    window.then(window => {
      let window = windowStage.getMainWindow()
      uicontext.runScopedTask(() => {
        Environment.envProp('languageCode', 'en');
      })
    })
  }
}
  • 17
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值