鴻蒙開發(fā)狀態(tài)管理

在聲明式UI編程框架中滞磺,UI是程序狀態(tài)的運(yùn)行結(jié)果,用戶構(gòu)建了一個UI模型涵卵,其中應(yīng)用的運(yùn)行時的狀態(tài)是參數(shù)浴栽。當(dāng)參數(shù)改變時,UI作為返回結(jié)果轿偎,也將進(jìn)行對應(yīng)的改變典鸡。這些運(yùn)行時的狀態(tài)變化所帶來的UI的重新渲染,在ArkUI中統(tǒng)稱為狀態(tài)管理機(jī)制坏晦。

自定義組件擁有變量萝玷,變量必須被裝飾器裝飾才可以成為狀態(tài)變量嫁乘,狀態(tài)變量的改變會引起UI的渲染刷新。如果不使用狀態(tài)變量球碉,UI只能在初始化時渲染蜓斧,后續(xù)將不會再刷新。 下圖展示了State和View(UI)之間的關(guān)系睁冬。

  • View(UI):UI渲染挎春,指將build方法內(nèi)的UI描述和@Builder裝飾的方法內(nèi)的UI描述映射到界面。
  • State:狀態(tài)豆拨,指驅(qū)動UI更新的數(shù)據(jù)搂蜓。用戶通過觸發(fā)組件的事件方法,改變狀態(tài)數(shù)據(jù)辽装。狀態(tài)數(shù)據(jù)的改變,引起UI的重新渲染相味。

基本概念

  • 狀態(tài)變量:被狀態(tài)裝飾器裝飾的變量拾积,狀態(tài)變量值的改變會引起UI的渲染更新。示例:@State num: number = 1,其中丰涉,@State是狀態(tài)裝飾器拓巧,num是狀態(tài)變量。
  • 常規(guī)變量:沒有被狀態(tài)裝飾器裝飾的變量一死,通常應(yīng)用于輔助計算肛度。它的改變永遠(yuǎn)不會引起UI的刷新。以下示例中increaseBy變量為常規(guī)變量投慈。
  • 數(shù)據(jù)源/同步源:狀態(tài)變量的原始來源承耿,可以同步給不同的狀態(tài)數(shù)據(jù)。通常意義為父組件傳給子組件的數(shù)據(jù)伪煤。以下示例中數(shù)據(jù)源為count: 1加袋。
  • 命名參數(shù)機(jī)制:父組件通過指定參數(shù)傳遞給子組件的狀態(tài)變量,為父子傳遞同步參數(shù)的主要手段抱既。示例:CompA: ({ aProp: this.aProp })职烧。
  • 從父組件初始化:父組件使用命名參數(shù)機(jī)制,將指定參數(shù)傳遞給子組件防泵。子組件初始化的默認(rèn)值在有父組件傳值的情況下蚀之,會被覆蓋。示例:
@Component
struct MyComponent {
  @State count: number = 0;
  private increaseBy: number = 1;

  build() {
  }
}

@Component
struct Parent {
  build() {
    Column() {
      // 從父組件初始化捷泞,覆蓋本地定義的默認(rèn)值
      MyComponent({ count: 1, increaseBy: 2 })
    }
  }
}
  • 初始化子節(jié)點(diǎn):父組件中狀態(tài)變量可以傳遞給子組件足删,初始化子組件對應(yīng)的狀態(tài)變量。示例同上肚邢。
  • 本地初始化:在變量聲明的時候賦值壹堰,作為變量的默認(rèn)值拭卿。示例:@State count: number = 0。

裝飾器總覽

ArkUI提供了多種裝飾器贱纠,通過使用這些裝飾器峻厚,狀態(tài)變量不僅可以觀察在組件內(nèi)的改變,還可以在不同組件層級間傳遞谆焊,比如父子組件惠桃、跨組件層級,也可以觀察全局范圍內(nèi)的變化辖试。根據(jù)狀態(tài)變量的影響范圍辜王,將所有的裝飾器可以大致分為:

  • 管理組件擁有狀態(tài)的裝飾器:組件級別的狀態(tài)管理,可以觀察組件內(nèi)變化罐孝,和不同組件層級的變化呐馆,但需要唯一觀察同一個組件樹上,即同一個頁面內(nèi)莲兢。
  • 管理應(yīng)用擁有狀態(tài)的裝飾器:應(yīng)用級別的狀態(tài)管理汹来,可以觀察不同頁面,甚至不同UIAbility的狀態(tài)變化改艇,是應(yīng)用內(nèi)全局的狀態(tài)管理收班。

從數(shù)據(jù)的傳遞形式和同步類型層面看,裝飾器也可分為:

  • 只讀的單向傳遞谒兄;
  • 可變更的雙向傳遞摔桦。

圖示如下,具體裝飾器的介紹承疲,可詳見管理組件擁有的狀態(tài)管理應(yīng)用擁有的狀態(tài)邻耕。開發(fā)者可以靈活地利用這些能力來實(shí)現(xiàn)數(shù)據(jù)和UI的聯(lián)動。


上圖中纪隙,Components部分的裝飾器為組件級別的狀態(tài)管理赊豌,Application部分為應(yīng)用的狀態(tài)管理。開發(fā)者可以通過@StorageLink/@LocalStorageLink實(shí)現(xiàn)應(yīng)用和組件狀態(tài)的雙向同步绵咱,通過@StorageProp/@LocalStorageProp實(shí)現(xiàn)應(yīng)用和組件狀態(tài)的單向同步碘饼。

管理組件擁有的狀態(tài),即圖中Components級別的狀態(tài)管理:

  • @State:@State裝飾的變量擁有其所屬組件的狀態(tài)悲伶,可以作為其子組件單向和雙向同步的數(shù)據(jù)源艾恼。當(dāng)其數(shù)值改變時,會引起相關(guān)組件的渲染刷新麸锉。
  • @Prop:@Prop裝飾的變量可以和父組件建立單向同步關(guān)系钠绍,@Prop裝飾的變量是可變的,但修改不會同步回父組件花沉。
  • @Link:@Link裝飾的變量和父組件構(gòu)建雙向同步關(guān)系的狀態(tài)變量柳爽,父組件會接受來自@Link裝飾的變量的修改的同步媳握,父組件的更新也會同步給@Link裝飾的變量。
  • @Provide/@Consume:@Provide/@Consume裝飾的變量用于跨組件層級(多層組件)同步狀態(tài)變量磷脯,可以不需要通過參數(shù)命名機(jī)制傳遞蛾找,通過alias(別名)或者屬性名綁定。
  • @Observed:@Observed裝飾class赵誓,需要觀察多層嵌套場景的class需要被@Observed裝飾打毛。單獨(dú)使用@Observed沒有任何作用,需要和@ObjectLink俩功、@Prop連用幻枉。
  • @ObjectLink:@ObjectLink裝飾的變量接收@Observed裝飾的class的實(shí)例,應(yīng)用于觀察多層嵌套場景诡蜓,和父組件的數(shù)據(jù)源構(gòu)建雙向同步熬甫。
說明

僅[@Observed/@ObjectLink]可以觀察嵌套場景,其他的狀態(tài)變量僅能觀察第一層蔓罚,
詳情見各個裝飾器章節(jié)的“觀察變化和行為表現(xiàn)”小節(jié)罗珍。

管理應(yīng)用擁有的狀態(tài),即圖中Application級別的狀態(tài)管理:

  • AppStorage是應(yīng)用程序中的一個特殊的單例LocalStorage對象脚粟,是應(yīng)用級的數(shù)據(jù)庫,和進(jìn)程綁定蘸朋,通過@StorageProp@StorageLink裝飾器可以和組件聯(lián)動核无。
  • AppStorage是應(yīng)用狀態(tài)的“中樞”,將需要與組件(UI)交互的數(shù)據(jù)存入AppStorage藕坯,比如持久化數(shù)據(jù)PersistentStorage和環(huán)境變量Environment团南。UI再通過AppStorage提供的裝飾器或者API接口,訪問這些數(shù)據(jù)炼彪。
  • 框架還提供了LocalStorage吐根,AppStorage是LocalStorage特殊的單例。LocalStorage是應(yīng)用程序聲明的應(yīng)用狀態(tài)的內(nèi)存“數(shù)據(jù)庫”辐马,通常用于頁面級的狀態(tài)共享拷橘,通過@LocalStorageProp@LocalStorageLink裝飾器可以和UI聯(lián)動。

其他狀態(tài)管理功能

@Watch用于監(jiān)聽狀態(tài)變量的變化喜爷。

$$運(yùn)算符:給內(nèi)置組件提供TS變量的引用冗疮,使得TS變量和內(nèi)置組件的內(nèi)部狀態(tài)保持同步。

@State裝飾器:組件內(nèi)狀態(tài)

@State裝飾的變量檩帐,與聲明式范式中的其他被裝飾變量一樣术幔,是私有的,只能從組件內(nèi)部訪問湃密,在聲明時必須指定其類型和本地初始化诅挑。初始化也可選擇使用命名參數(shù)機(jī)制從父組件完成初始化四敞。

@State裝飾的變量擁有以下特點(diǎn):

@State裝飾的變量與子組件中的@Prop裝飾變量之間建立單向數(shù)據(jù)同步,與@Link拔妥、@ObjectLink裝飾變量之間建立雙向數(shù)據(jù)同步忿危。
@State裝飾的變量生命周期與其所屬自定義組件的生命周期相同。

class Model {
  public value: string;

  constructor(value: string) {
    this.value = value;
  }
}

@Entry
@Component
struct EntryComponent {
  build() {
    Column() {
      // 此處指定的參數(shù)都將在初始渲染時覆蓋本地定義的默認(rèn)值毒嫡,并不是所有的參數(shù)都需要從父組件初始化
      MyComponent({ count: 1, increaseBy: 2 })
        .width(300)
      MyComponent({ title: new Model('Hello World 2'), count: 7 })
    }
  }
}

@Component
struct MyComponent {
  @State title: Model = new Model('Hello World');
  @State count: number = 0;
  private increaseBy: number = 1;

  build() {
    Column() {
      Text(`${this.title.value}`)
        .margin(10)
      Button(`Click to change title`)
        .onClick(() => {
          // @State變量的更新將觸發(fā)上面的Text組件內(nèi)容更新
          this.title.value = this.title.value === 'Hello ArkUI' ? 'Hello World' : 'Hello ArkUI';
        })
        .width(300)
        .margin(10)

      Button(`Click to increase count = ${this.count}`)
        .onClick(() => {
          // @State變量的更新將觸發(fā)該Button組件的內(nèi)容更新
          this.count += this.increaseBy;
        })
        .width(300)
        .margin(10)
    }
  }
}
@Prop裝飾器:父子單向同步

@Prop裝飾的變量和父組件建立單向的同步關(guān)系:

@Prop變量允許在本地修改癌蚁,但修改后的變化不會同步回父組件。
當(dāng)父組件中的數(shù)據(jù)源更改時兜畸,與之相關(guān)的@Prop裝飾的變量都會自動更新努释。如果子組件已經(jīng)在本地修改了@Prop裝飾的相關(guān)變量值,而在父組件中對應(yīng)的@State裝飾的變量被修改后咬摇,子組件本地修改的@Prop裝飾的相關(guān)變量值將被覆蓋伐蒂。

@Component
struct CountDownComponent {
  @Prop count: number;
  costOfOneAttempt: number = 1;

  build() {
    Column() {
      if (this.count > 0) {
        Text(`You have ${this.count} Nuggets left`)
      } else {
        Text('Game over!')
      }
      // @Prop裝飾的變量不會同步給父組件
      Button(`Try again`).onClick(() => {
        this.count -= this.costOfOneAttempt;
      })
    }
  }
}

@Entry
@Component
struct ParentComponent {
  @State countDownStartValue: number = 10;

  build() {
    Column() {
      Text(`Grant ${this.countDownStartValue} nuggets to play.`)
      // 父組件的數(shù)據(jù)源的修改會同步給子組件
      Button(`+1 - Nuggets in New Game`).onClick(() => {
        this.countDownStartValue += 1;
      })
      // 父組件的修改會同步給子組件
      Button(`-1  - Nuggets in New Game`).onClick(() => {
        this.countDownStartValue -= 1;
      })

      CountDownComponent({ count: this.countDownStartValue, costOfOneAttempt: 2 })
    }
  }
}
@Link裝飾器:父子雙向同步

@Link裝飾的變量與其父組件中的數(shù)據(jù)源共享相同的值。

class GreenButtonState {
  width: number = 0;

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

@Component
struct GreenButton {
  @Link greenButtonState: GreenButtonState;

  build() {
    Button('Green Button')
      .width(this.greenButtonState.width)
      .height(40)
      .backgroundColor('#64bb5c')
      .fontColor('#FFFFFF肛鹏,90%')
      .onClick(() => {
        if (this.greenButtonState.width < 700) {
          // 更新class的屬性逸邦,變化可以被觀察到同步回父組件
          this.greenButtonState.width += 60;
        } else {
          // 更新class,變化可以被觀察到同步回父組件
          this.greenButtonState = new GreenButtonState(180);
        }
      })
  }
}

@Component
struct YellowButton {
  @Link yellowButtonState: number;

  build() {
    Button('Yellow Button')
      .width(this.yellowButtonState)
      .height(40)
      .backgroundColor('#f7ce00')
      .fontColor('#FFFFFF在扰,90%')
      .onClick(() => {
        // 子組件的簡單類型可以同步回父組件
        this.yellowButtonState += 40.0;
      })
  }
}

@Entry
@Component
struct ShufflingContainer {
  @State greenButtonState: GreenButtonState = new GreenButtonState(180);
  @State yellowButtonProp: number = 180;

  build() {
    Column() {
      Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center }) {
        // 簡單類型從父組件@State向子組件@Link數(shù)據(jù)同步
        Button('Parent View: Set yellowButton')
          .width(312)
          .height(40)
          .margin(12)
          .fontColor('#FFFFFF缕减,90%')
          .onClick(() => {
            this.yellowButtonProp = (this.yellowButtonProp < 700) ? this.yellowButtonProp + 40 : 100;
          })
        // class類型從父組件@State向子組件@Link數(shù)據(jù)同步
        Button('Parent View: Set GreenButton')
          .width(312)
          .height(40)
          .margin(12)
          .fontColor('#FFFFFF,90%')
          .onClick(() => {
            this.greenButtonState.width = (this.greenButtonState.width < 700) ? this.greenButtonState.width + 100 : 100;
          })
        // class類型初始化@Link
        GreenButton({ greenButtonState: $greenButtonState }).margin(12)
        // 簡單類型初始化@Link
        YellowButton({ yellowButtonState: $yellowButtonProp }).margin(12)
      }
    }
  }
}
@Provide裝飾器和@Consume裝飾器:與后代組件雙向同步

@Provide和@Consume芒珠,應(yīng)用于與后代組件的雙向數(shù)據(jù)同步桥狡,應(yīng)用于狀態(tài)數(shù)據(jù)在多個層級之間傳遞的場景。不同于上文提到的父子組件之間通過命名參數(shù)機(jī)制傳遞皱卓,@Provide和@Consume擺脫參數(shù)傳遞機(jī)制的束縛裹芝,實(shí)現(xiàn)跨層級傳遞。

其中@Provide裝飾的變量是在祖先節(jié)點(diǎn)中娜汁,可以理解為被“提供”給后代的狀態(tài)變量嫂易。@Consume裝飾的變量是在后代組件中,去“消費(fèi)(綁定)”祖先節(jié)點(diǎn)提供的變量掐禁。

@Provide/@Consume裝飾的狀態(tài)變量有以下特性:

@Provide裝飾的狀態(tài)變量自動對其所有后代組件可用怜械,即該變量被“provide”給他的后代組件。由此可見傅事,@Provide的方便之處在于宫盔,開發(fā)者不需要多次在組件之間傳遞變量。
后代通過使用@Consume去獲取@Provide提供的變量享完,建立在@Provide和@Consume之間的雙向數(shù)據(jù)同步灼芭,與@State/@Link不同的是,前者可以在多層級的父子組件之間傳遞般又。
@Provide和@Consume可以通過相同的變量名或者相同的變量別名綁定彼绷,變量類型必須相同巍佑。

@Component
struct CompD {
  // @Consume裝飾的變量通過相同的屬性名綁定其祖先組件CompA內(nèi)的@Provide裝飾的變量
  @Consume reviewVotes: number;

  build() {
    Column() {
      Text(`reviewVotes(${this.reviewVotes})`)
      Button(`reviewVotes(${this.reviewVotes}), give +1`)
        .onClick(() => this.reviewVotes += 1)
    }
    .width('50%')
  }
}

@Component
struct CompC {
  build() {
    Row({ space: 5 }) {
      CompD()
      CompD()
    }
  }
}

@Component
struct CompB {
  build() {
    CompC()
  }
}

@Entry
@Component
struct CompA {
  // @Provide裝飾的變量reviewVotes由入口組件CompA提供其后代組件
  @Provide reviewVotes: number = 0;

  build() {
    Column() {
      Button(`reviewVotes(${this.reviewVotes}), give +1`)
        .onClick(() => this.reviewVotes += 1)
      CompB()
    }
  }
}
@Observed裝飾器和@ObjectLink裝飾器:嵌套類對象屬性變化

上文所述的裝飾器僅能觀察到第一層的變化,但是在實(shí)際應(yīng)用開發(fā)中寄悯,應(yīng)用會根據(jù)開發(fā)需要萤衰,封裝自己的數(shù)據(jù)模型。對于多層嵌套的情況猜旬,比如二維數(shù)組脆栋,或者數(shù)組項(xiàng)class,或者class的屬性是class洒擦,他們的第二層的屬性變化是無法觀察到的椿争。這就引出了@Observed/@ObjectLink裝飾器。

@ObjectLink和@Observed類裝飾器用于在涉及嵌套對象或數(shù)組的場景中進(jìn)行雙向數(shù)據(jù)同步:

被@Observed裝飾的類熟嫩,可以被觀察到屬性的變化秦踪;
子組件中@ObjectLink裝飾器裝飾的狀態(tài)變量用于接收@Observed裝飾的類的實(shí)例,和父組件中對應(yīng)的狀態(tài)變量建立雙向數(shù)據(jù)綁定掸茅。這個實(shí)例可以是數(shù)組中的被@Observed裝飾的項(xiàng)椅邓,或者是class object中的屬性,這個屬性同樣也需要被@Observed裝飾昧狮。
單獨(dú)使用@Observed是沒有任何作用的景馁,需要搭配@ObjectLink或者@Prop使用。

@Observed
class StringArray extends Array<String> {
}

@Component
struct ItemPage {
  @ObjectLink itemArr: StringArray;

  build() {
    Row() {
      Text('ItemPage')
        .width(100).height(100)

      ForEach(this.itemArr,
        item => {
          Text(item)
            .width(100).height(100)
        },
        item => item
      )
    }
  }
}

@Entry
@Component
struct IndexPage {
  @State arr: Array<StringArray> = [new StringArray(), new StringArray(), new StringArray()];

  build() {
    Column() {
      ItemPage({ itemArr: this.arr[0] })
      ItemPage({ itemArr: this.arr[1] })
      ItemPage({ itemArr: this.arr[2] })

      Divider()

      ForEach(this.arr,
        itemArr => {
          ItemPage({ itemArr: itemArr })
        },
        itemArr => itemArr[0]
      )

      Divider()

      Button('update')
        .onClick(() => {
          console.error('Update all items in arr');
          if (this.arr[0][0] !== undefined) {
            // 正常情況下需要有一個真實(shí)的ID來與ForEach一起使用逗鸣,但此處沒有
            // 因此需要確保推送的字符串是唯一的裁僧。
            this.arr[0].push(`${this.arr[0].slice(-1).pop()}${this.arr[0].slice(-1).pop()}`);
            this.arr[1].push(`${this.arr[1].slice(-1).pop()}${this.arr[1].slice(-1).pop()}`);
            this.arr[2].push(`${this.arr[2].slice(-1).pop()}${this.arr[2].slice(-1).pop()}`);
          } else {
            this.arr[0].push('Hello');
            this.arr[1].push('World');
            this.arr[2].push('!');
          }
        })
    }
  }
}

上文中介紹的裝飾器僅能在頁面內(nèi),即一個組件樹上共享狀態(tài)變量慕购。如果開發(fā)者要實(shí)現(xiàn)應(yīng)用級的,或者多個頁面的狀態(tài)數(shù)據(jù)共享茬底,就需要用到應(yīng)用級別的狀態(tài)管理的概念沪悲。ArkTS根據(jù)不同特性,提供了多種應(yīng)用狀態(tài)管理的能力:

  • LocalStorage:頁面級UI狀態(tài)存儲阱表,通常用于UIAbility內(nèi)殿如、頁面間的狀態(tài)共享。
  • AppStorage:特殊的單例LocalStorage對象最爬,由UI框架在應(yīng)用程序啟動時創(chuàng)建涉馁,為應(yīng)用程序UI狀態(tài)屬性提供中央存儲;
  • PersistentStorage:持久化存儲UI狀態(tài)爱致,通常和AppStorage配合使用烤送,選擇AppStorage存儲的數(shù)據(jù)寫入磁盤,以確保這些屬性在應(yīng)用程序重新啟動時的值與應(yīng)用程序關(guān)閉時的值相同糠悯;
  • Environment:應(yīng)用程序運(yùn)行的設(shè)備的環(huán)境參數(shù)帮坚,環(huán)境參數(shù)會同步到AppStorage中妻往,可以和AppStorage搭配使用。
LocalStorage:頁面級UI狀態(tài)存儲

LocalStorage是頁面級的UI狀態(tài)存儲试和,通過@Entry裝飾器接收的參數(shù)可以在頁面內(nèi)共享同一個LocalStorage實(shí)例讯泣。LocalStorage也可以在UIAbility實(shí)例內(nèi),在頁面間共享狀態(tài)阅悍。

LocalStorage是ArkTS為構(gòu)建頁面級別狀態(tài)變量提供存儲的內(nèi)存內(nèi)“數(shù)據(jù)庫”好渠。

  • 應(yīng)用程序可以創(chuàng)建多個LocalStorage實(shí)例,LocalStorage實(shí)例可以在頁面內(nèi)共享节视,也可以通過GetShared接口拳锚,實(shí)現(xiàn)跨頁面、UIAbility實(shí)例內(nèi)共享肴茄。
  • 組件樹的根節(jié)點(diǎn)晌畅,即被@Entry裝飾的@Component,可以被分配一個LocalStorage實(shí)例寡痰,此組件的所有子組件實(shí)例將自動獲得對該LocalStorage實(shí)例的訪問權(quán)限抗楔;
  • 被@Component裝飾的組件最多可以訪問一個LocalStorage實(shí)例和AppStorage,未被@Entry裝飾的組件不可被獨(dú)立分配LocalStorage實(shí)例拦坠,只能接受父組件通過@Entry傳遞來的LocalStorage實(shí)例连躏。一個LocalStorage實(shí)例在組件樹上可以被分配給多個組件。
  • LocalStorage中的所有屬性都是可變的贞滨。

應(yīng)用程序決定LocalStorage對象的生命周期入热。當(dāng)應(yīng)用釋放最后一個指向LocalStorage的引用時,比如銷毀最后一個自定義組件晓铆,LocalStorage將被JS Engine垃圾回收勺良。

LocalStorage根據(jù)與@Component裝飾的組件的同步類型不同,提供了兩個裝飾器:

  • @LocalStorageProp:@LocalStorageProp裝飾的變量和與LocalStorage中給定屬性建立單向同步關(guān)系骄噪。
  • @LocalStorageLink:@LocalStorageLink裝飾的變量和在@Component中創(chuàng)建與LocalStorage中給定屬性建立雙向同步關(guān)系尚困。
AppStorage:應(yīng)用全局的UI狀態(tài)存儲

AppStorage是應(yīng)用全局的UI狀態(tài)存儲,是和應(yīng)用的進(jìn)程綁定的链蕊,由UI框架在應(yīng)用程序啟動時創(chuàng)建事甜,為應(yīng)用程序UI狀態(tài)屬性提供中央存儲。

和AppStorage不同的是滔韵,LocalStorage是頁面級的逻谦,通常應(yīng)用于頁面內(nèi)的數(shù)據(jù)共享。而AppStorage是應(yīng)用級的全局狀態(tài)共享陪蜻,還相當(dāng)于整個應(yīng)用的“中樞”邦马,持久化數(shù)據(jù)PersistentStorage環(huán)境變量Environment都是通過和AppStorage中轉(zhuǎn),才可以和UI交互。

本文僅介紹AppStorage使用場景和相關(guān)的裝飾器:@StorageProp和@StorageLink勇婴。

AppStorage是在應(yīng)用啟動的時候會被創(chuàng)建的單例忱嘹。它的目的是為了提供應(yīng)用狀態(tài)數(shù)據(jù)的中心存儲,這些狀態(tài)數(shù)據(jù)在應(yīng)用級別都是可訪問的耕渴。AppStorage將在應(yīng)用運(yùn)行過程保留其屬性拘悦。屬性通過唯一的鍵字符串值訪問。

AppStorage可以和UI組件同步橱脸,且可以在應(yīng)用業(yè)務(wù)邏輯中被訪問础米。

AppStorage中的屬性可以被雙向同步,數(shù)據(jù)可以是存在于本地或遠(yuǎn)程設(shè)備上添诉,并具有不同的功能屁桑,比如數(shù)據(jù)持久化(詳見PersistentStorage)忱反。這些數(shù)據(jù)是通過業(yè)務(wù)邏輯中實(shí)現(xiàn)炕横,與UI解耦,如果希望這些數(shù)據(jù)在UI中使用边翼,需要用到@StorageProp@StorageLink须眷。

PersistentStorage:持久化存儲UI狀態(tài)

前兩個小節(jié)介紹的LocalStorage和AppStorage都是運(yùn)行時的內(nèi)存竖瘾,但是在應(yīng)用退出再次啟動后,依然能保存選定的結(jié)果花颗,是應(yīng)用開發(fā)中十分常見的現(xiàn)象捕传,這就需要用到PersistentStorage。

PersistentStorage是應(yīng)用程序中的可選單例對象扩劝。此對象的作用是持久化存儲選定的AppStorage屬性庸论,以確保這些屬性在應(yīng)用程序重新啟動時的值與應(yīng)用程序關(guān)閉時的值相同。

PersistentStorage將選定的AppStorage屬性保留在設(shè)備磁盤上棒呛。應(yīng)用程序通過API聂示,以決定哪些AppStorage屬性應(yīng)借助PersistentStorage持久化。UI和業(yè)務(wù)邏輯不直接訪問PersistentStorage中的屬性簇秒,所有屬性訪問都是對AppStorage的訪問鱼喉,AppStorage中的更改會自動同步到PersistentStorage。

PersistentStorage和AppStorage中的屬性建立雙向同步宰睡。應(yīng)用開發(fā)通常通過AppStorage訪問PersistentStorage,另外還有一些接口可以用于管理持久化屬性气筋,但是業(yè)務(wù)邏輯始終是通過AppStorage獲取和設(shè)置屬性的拆内。

限制條件

PersistentStorage允許的類型和值有:

  • number, string, boolean, enum 等簡單類型。
  • 可以被JSON.stringify()和JSON.parse()重構(gòu)的對象宠默。例如Date, Map, Set等內(nèi)置類型則不支持麸恍,以及對象的屬性方法不支持持久化。

PersistentStorage不允許的類型和值有:

  • 不支持嵌套對象(對象數(shù)組,對象的屬性是對象等)抹沪。因?yàn)槟壳翱蚣軣o法檢測AppStorage中嵌套對象(包括數(shù)組)值的變化刻肄,所以無法寫回到PersistentStorage中。
  • 不支持undefined 和 null 融欧。

持久化數(shù)據(jù)是一個相對緩慢的操作敏弃,應(yīng)用程序應(yīng)避免以下情況:

  • 持久化大型數(shù)據(jù)集。
  • 持久化經(jīng)常變化的變量噪馏。

PersistentStorage的持久化變量最好是小于2kb的數(shù)據(jù)麦到,不要大量的數(shù)據(jù)持久化,因?yàn)镻ersistentStorage寫入磁盤的操作是同步的欠肾,大量的數(shù)據(jù)本地化讀寫會同步在UI線程中執(zhí)行瓶颠,影響UI渲染性能。如果開發(fā)者需要存儲大量的數(shù)據(jù)刺桃,建議使用數(shù)據(jù)庫api粹淋。

PersistentStorage只能在UI頁面內(nèi)使用,否則將無法持久化數(shù)據(jù)瑟慈。

Environment:設(shè)備環(huán)境查詢

開發(fā)者如果需要應(yīng)用程序運(yùn)行的設(shè)備的環(huán)境參數(shù)桃移,以此來作出不同的場景判斷,比如多語言封豪,暗黑模式等谴轮,需要用到Environment設(shè)備環(huán)境查詢。

Environment是ArkUI框架在應(yīng)用程序啟動時創(chuàng)建的單例對象吹埠。它為AppStorage提供了一系列描述應(yīng)用程序運(yùn)行狀態(tài)的屬性第步。Environment的所有屬性都是不可變的(即應(yīng)用不可寫入),所有的屬性都是簡單類型缘琅。

// 將設(shè)備languageCode存入AppStorage中
Environment.EnvProp('languageCode', 'en');

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

  build() {
    Row() {
      Column() {
        // 輸出當(dāng)前設(shè)備的languageCode
        Text(this.languageCode)
      }
    }
  }
}

應(yīng)用邏輯使用Environment

// 使用Environment.EnvProp將設(shè)備運(yùn)行l(wèi)anguageCode存入AppStorage中粘都;
Environment.EnvProp('languageCode', 'en');
// 從AppStorage獲取單向綁定的languageCode的變量
const lang: SubscribedAbstractProperty<string> = AppStorage.Prop('languageCode');

if (lang.get() === 'zh') {
  console.info('你好');
} else {
  console.info('Hello!');
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市刷袍,隨后出現(xiàn)的幾起案子翩隧,更是在濱河造成了極大的恐慌,老刑警劉巖呻纹,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件堆生,死亡現(xiàn)場離奇詭異,居然都是意外死亡雷酪,警方通過查閱死者的電腦和手機(jī)淑仆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來哥力,“玉大人蔗怠,你說我怎么就攤上這事墩弯。” “怎么了寞射?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵渔工,是天一觀的道長。 經(jīng)常有香客問我桥温,道長引矩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任策治,我火速辦了婚禮脓魏,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘通惫。我一直安慰自己茂翔,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布履腋。 她就那樣靜靜地躺著珊燎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪遵湖。 梳的紋絲不亂的頭發(fā)上悔政,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天,我揣著相機(jī)與錄音延旧,去河邊找鬼谋国。 笑死,一個胖子當(dāng)著我的面吹牛迁沫,可吹牛的內(nèi)容都是我干的芦瘾。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼集畅,長吁一口氣:“原來是場噩夢啊……” “哼近弟!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起挺智,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤祷愉,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后赦颇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體二鳄,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年媒怯,在試婚紗的時候發(fā)現(xiàn)自己被綠了订讼。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡沪摄,死狀恐怖躯嫉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情杨拐,我是刑警寧澤祈餐,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站哄陶,受9級特大地震影響帆阳,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜屋吨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一蜒谤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧至扰,春花似錦鳍徽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至直秆,卻和暖如春濒募,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背圾结。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工瑰剃, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人筝野。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓晌姚,卻偏偏與公主長得像,于是被迫代替她去往敵國和親遗座。 傳聞我的和親對象是個殘疾皇子舀凛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容