OpenHarmony頁面級UI狀態儲存:LocalStorage

OpenHarmony開發者社群發表於2023-10-17

LocalStorage是頁面級的UI狀態儲存,透過@Entry裝飾器接收的引數可以在頁面內共享同一個LocalStorage例項。LocalStorage也可以在UIAbility內,頁面間共享狀態。

本文僅介紹LocalStorage使用場景和相關的裝飾器:@LocalStorageProp和@LocalStorageLink。

說明:

LocalStorage從API version 9開始支援。

概述

LocalStorage是ArkTS為構建頁面級別狀態變數提供儲存的記憶體內“資料庫”。

●  應用程式可以建立多個LocalStorage例項,LocalStorage例項可以在頁面內共享,也可以透過GetShared介面,獲取在UIAbility裡建立的GetShared,實現跨頁面、UIAbility內共享。

●  元件樹的根節點,即被@Entry裝飾的@Component,可以被分配一個LocalStorage例項,此元件的所有子元件例項將自動獲得對該LocalStorage例項的訪問許可權。

●  被@Component裝飾的元件最多可以訪問一個LocalStorage例項和 AppStorage ,未被@Entry裝飾的元件不可被獨立分配LocalStorage例項,只能接受父元件透過@Entry傳遞來的LocalStorage例項。一個LocalStorage例項在元件樹上可以被分配給多個元件。

●  LocalStorage中的所有屬性都是可變的。

應用程式決定LocalStorage物件的生命週期。當應用釋放最後一個指向LocalStorage的引用時,比如銷燬最後一個自定義元件,LocalStorage將被JS Engine垃圾回收。

LocalStorage根據與@Component裝飾的元件的同步型別不同,提供了兩個裝飾器:

●  @LocalStorageProp :@LocalStorageProp裝飾的變數和與LocalStorage中給定屬性建立單向同步關係。

●  @LocalStorageLink :@LocalStorageLink裝飾的變數和在@Component中建立與LocalStorage中給定屬性建立雙向同步關係。

限制條件

●  LocalStorage建立後,命名屬性的型別不可更改。後續呼叫Set時必須使用相同型別的值。

●  LocalStorage是頁面級儲存, GetShared 介面僅能獲取當前Stage透過 windowStage.loadContent 傳入的LocalStorage例項,否則返回undefined。例子可見 將LocalStorage例項從UIAbility共享到一個或多個檢視

@LocalStorageProp

在上文中已經提到,如果要建立LocalStorage和自定義元件的聯絡,需要使用@LocalStorageProp和@LocalStorageLink裝飾器。使用@LocalStorageProp(key)/@LocalStorageLink(key)裝飾元件內的變數,key標識了LocalStorage的屬性。

當自定義元件初始化的時候,@LocalStorageProp(key)/@LocalStorageLink(key)裝飾的變數會透過給定的key,繫結LocalStorage對應的屬性,完成初始化。本地初始化是必要的,因為無法保證LocalStorage一定存在給定的key(這取決於應用邏輯是否在元件初始化之前在LocalStorage例項中存入對應的屬性)。

說明:

從API version 9開始,該裝飾器支援在ArkTS卡片中使用。

@LocalStorageProp(key)是和LocalStorage中key對應的屬性建立單向資料同步,我們允許本地改變的發生,但是對於@LocalStorageProp,本地的修改永遠不會同步回LocalStorage中,相反,如果LocalStorage給定key的屬性發生改變,改變會被同步給@LocalStorageProp,並覆蓋掉本地的修改。

裝飾器使用規則說明

@LocalStorageProp變數裝飾器

說明

裝飾器引數

key:常量字串,必填(字串需要有引號)。

允許裝飾的變數型別

Object、class、string、number、boolean、enum型別,以及這些型別的陣列。巢狀型別的場景請參考 觀察變化和行為表現

型別必須被指定,建議和LocalStorage中對應屬性型別相同,否則會發生型別隱式轉換,從而導致應用行為異常。不支援any,不允許使用undefined和null。

同步型別

單向同步:從LocalStorage的對應屬性到元件的狀態變數。元件本地的修改是允許的,但是LocalStorage中給定的屬性一旦發生變化,將覆蓋本地的修改。

被裝飾變數的初始值

必須指定,如果LocalStorage例項中不存在屬性,則作為初始化預設值,並存入LocalStorage中。

變數的傳遞/訪問規則說明

傳遞/訪問

說明

從父節點初始化和更新

禁止,@LocalStorageProp不支援從父節點初始化,只能從LocalStorage中key對應的屬性初始化,如果沒有對應key的話,將使用本地預設值初始化。

初始化子節點

支援,可用於初始化@State、@Link、@Prop、@Provide。

是否支援元件外訪問

否。

圖1 @LocalStorageProp初始化規則圖示  

觀察變化和行為表現

觀察變化

●  當裝飾的資料型別為boolean、string、number型別時,可以觀察到數值的變化。

●  當裝飾的資料型別為class或者Object時,可以觀察到賦值和屬性賦值的變化,即Object.keys(observedObject)返回的所有屬性。

●  當裝飾的物件是array時,可以觀察到陣列新增、刪除、更新陣列單元的變化。

框架行為

●  當@LocalStorageProp(key)裝飾的數值改變被觀察到時,修改不會被同步回LocalStorage對應屬性鍵值key的屬性中。

●  當前@LocalStorageProp(key)單向繫結的資料會被修改,即僅限於當前元件的私有成員變數改變,其他的繫結該key的資料不會同步改變。

●  當@LocalStorageProp(key)裝飾的資料本身是狀態變數,它的改變雖然不會同步回LocalStorage中,但是會引起所屬的自定義元件的重新渲染。

●  當LocalStorage中key對應的屬性發生改變時,會同步給所有@LocalStorageProp(key)裝飾的資料,@LocalStorageProp(key)本地的修改將被覆蓋。

@LocalStorageLink

如果我們需要將自定義元件的狀態變數的更新同步回LocalStorage,就需要用到@LocalStorageLink。

@LocalStorageLink(key)是和LocalStorage中key對應的屬性建立雙向資料同步:

1.      本地修改發生,該修改會被回LocalStorage中;

2.      LocalStorage中的修改發生後,該修改會被同步到所有繫結LocalStorage對應key的屬性上,包括單向(@LocalStorageProp和透過prop建立的單向繫結變數)、雙向(@LocalStorageLink和透過link建立的雙向繫結變數)變數。

裝飾器使用規則說明

@LocalStorageLink變數裝飾器

說明

裝飾器引數

key:常量字串,必填(字串需要有引號)。

允許裝飾的變數型別

Object、class、string、number、boolean、enum型別,以及這些型別的陣列。巢狀型別的場景請參考 觀察變化和行為表現

型別必須被指定,建議和LocalStorage中對應屬性型別相同,否則會發生型別隱式轉換,從而導致應用行為異常。不支援any,不允許使用undefined和null。

同步型別

雙向同步:從LocalStorage的對應屬性到自定義元件,從自定義元件到LocalStorage對應屬性。

被裝飾變數的初始值

必須指定,如果LocalStorage例項中不存在屬性,則作為初始化預設值,並存入LocalStorage中。

變數的傳遞/訪問規則說明

傳遞/訪問

說明

從父節點初始化和更新

禁止,@LocalStorageLink不支援從父節點初始化,只能從LocalStorage中key對應的屬性初始化,如果沒有對應key的話,將使用本地預設值初始化。

初始化子節點

支援,可用於初始化@State、@Link、@Prop、@Provide。

是否支援元件外訪問

否。

圖2 @LocalStorageLink初始化規則圖示  

觀察變化和行為表現

觀察變化

●  當裝飾的資料型別為boolean、string、number型別時,可以觀察到數值的變化。

●  當裝飾的資料型別為class或者Object時,可以觀察到賦值和屬性賦值的變化,即Object.keys(observedObject)返回的所有屬性。

●  當裝飾的物件是array時,可以觀察到陣列新增、刪除、更新陣列單元的變化。

框架行為

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: LocalStorage = new LocalStorage(para); // 建立新例項並使用給定物件初始化
let propA: number | undefined = storage.get('PropA') // propA == 47
let link1: SubscribedAbstractProperty<number> = storage.link('PropA'); // link1.get() == 47
let link2: SubscribedAbstractProperty<number> = storage.link('PropA'); // link2.get() == 47
let prop: SubscribedAbstractProperty<number> = storage.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

從UI內部使用LocalStorage

除了應用程式邏輯使用LocalStorage,還可以藉助LocalStorage相關的兩個裝飾器@LocalStorageProp和@LocalStorageLink,在UI元件內部獲取到LocalStorage例項中儲存的狀態變數。

本示例以@LocalStorageLink為例,展示了:

●  使用建構函式建立LocalStorage例項storage;

●  使用@Entry裝飾器將storage新增到CompA頂層元件中;

●  @LocalStorageLink繫結LocalStorage對給定的屬性,建立雙向資料同步。

// 建立新例項並使用給定物件初始化
let para:Record<string,number> = { 'PropA': 47 };
let storage: LocalStorage = new LocalStorage(para);
@Component
struct Child {
  // @LocalStorageLink變數裝飾器與LocalStorage中的'PropA'屬性建立雙向繫結
  @LocalStorageLink('PropA') storLink2: number = 1;
  build() {
    Button(`Child from LocalStorage ${this.storLink2}`)
      // 更改將同步至LocalStorage中的'PropA'以及Parent.storLink1
      .onClick(() => this.storLink2 += 1)
  }
}
// 使LocalStorage可從@Component元件訪問
@Entry(storage)
@Component
struct CompA {
  // @LocalStorageLink變數裝飾器與LocalStorage中的'PropA'屬性建立雙向繫結
  @LocalStorageLink('PropA') storLink1: number = 1;
  build() {
    Column({ space: 15 }) {
      Button(`Parent from LocalStorage ${this.storLink1}`) // initial value from LocalStorage will be 47, because 'PropA' initialized already
        .onClick(() => this.storLink1 += 1)
      // @Component子元件自動獲得對CompA LocalStorage例項的訪問許可權。
      Child()
    }
  }
}

@LocalStorageProp和LocalStorage單向同步的簡單場景

在下面的示例中,CompA 元件和Child元件分別在本地建立了與storage的'PropA'對應屬性的單向同步的資料,我們可以看到:

●  CompA中對this.storProp1的修改,只會在CompA中生效,並沒有同步回storage;

●  Child元件中,Text繫結的storProp2 依舊顯示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') storProp1: number = 1;
  build() {
    Column({ space: 15 }) {
      // 點選後從47開始加1,只改變當前元件顯示的storProp1,不會同步到LocalStorage中
      Button(`Parent from LocalStorage ${this.storProp1}`)
        .onClick(() => this.storProp1 += 1)
      Child()
    }
  }
}
@Component
struct Child {
  // @LocalStorageProp變數裝飾器與LocalStorage中的'PropA'屬性建立單向繫結
  @LocalStorageProp('PropA') storProp2: number = 2;
  build() {
    Column({ space: 15 }) {
      // 當CompA改變時,當前storProp2不會改變,顯示47
      Text(`Parent from LocalStorage ${this.storProp2}`)
    }
  }
}

@LocalStorageLink和LocalStorage雙向同步的簡單場景

下面的示例展示了@LocalStorageLink裝飾的資料和LocalStorage雙向同步的場景:

// 構造LocalStorage例項
let para:Record<string,number> = { 'PropA': 47 };
let storage: LocalStorage = new LocalStorage(para);
// 呼叫link9+介面構造'PropA'的雙向同步資料,linkToPropA 是全域性變數
let linkToPropA: SubscribedAbstractProperty<object> = storage.link('PropA');
@Entry(storage)
@Component
struct CompA {
  // @LocalStorageLink('PropA')在CompA自定義元件中建立'PropA'的雙向同步資料,初始值為47,因為在構造LocalStorage已經給“PropA”設定47
  @LocalStorageLink('PropA') storLink: number = 1;
  build() {
    Column() {
      Text(`incr @LocalStorageLink variable`)
        // 點選“incr @LocalStorageLink variable”,this.storLink加1,改變同步回storage,全域性變數linkToPropA也會同步改變
        .onClick(() => this.storLink += 1)
      // 並不建議在元件內使用全域性變數linkToPropA.get(),因為可能會有生命週期不同引起的錯誤。
      Text(`@LocalStorageLink: ${this.storLink} - linkToPropA: ${linkToPropA.get()}`)
    }
  }
}

兄弟元件之間同步狀態變數

下面的示例展示了透過@LocalStorageLink雙向同步兄弟元件之間的狀態。

先看Parent自定義元件中發生的變化:

1.      點選“playCount ${this.playCount} dec by 1”,this.playCount減1,修改同步回LocalStorage中,Child元件中的playCountLink繫結的元件會同步重新整理;

2.      點選“countStorage ${this.playCount} incr by 1”,呼叫LocalStorage的set介面,更新LocalStorage中“countStorage”對應的屬性,Child元件中的playCountLink繫結的元件會同步重新整理;

3.      Text元件“playCount in LocalStorage for debug ${storage.get<number>('countStorage')}”沒有同步重新整理,因為storage.get<number>('countStorage')返回的是常規變數,常規變數的更新並不會引起Text元件的重新渲染。

Child自定義元件中的變化:

1.      playCountLink的重新整理會同步回LocalStorage,並且引起兄弟元件和父元件相應的重新整理。

let ls:Record<string,number> = { 'countStorage': 1}
let storage:LocalStorage = new LocalStorage(ls);
@Component
struct Child {
  // 子元件例項的名字
  label: string = 'no name';
  // 和LocalStorage中“countStorage”的雙向繫結資料
  @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(250).height(60).fontSize(12)
      }.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共享到一個或多個檢視

上面的例項中,LocalStorage的例項僅僅在一個@Entry裝飾的元件和其所屬的子元件(一個頁面)中-共享,如果希望其在多個檢視中-共享,可以在所屬UIAbility中建立LocalStorage例項,並呼叫windowStage. loadContent

// EntryAbility.ts
import UIAbility from '@ohos.app.ability.UIAbility';
import window from '@ohos.window';
let para:Record<string,number> = { 'PropA': 47 };
let localStorage: LocalStorage = new LocalStorage(para);
export default class EntryAbility extends UIAbility {
storage: LocalStorage = localStorage
onWindowStageCreate(windowStage: window.WindowStage) {
windowStage.loadContent('pages/Index', this.storage);
}
}

在UI頁面透過getShared介面獲取在透過loadContent共享的LocalStorage例項。

// 透過getShared介面獲取stage共享的LocalStorage例項
let storage = LocalStorage.getShared()
@Entry(storage)
@Component
struct CompA {
  // can access LocalStorage instance using 
  // @LocalStorageLink/Prop decorated variables
  @LocalStorageLink('PropA') varA: number = 1;
  build() {
    Column() {
      Text(`${this.varA}`).fontSize(50)
    }
  }
}

說明:

對於開發者更建議使用這個方式來構建LocalStorage的例項,並且在建立LocalStorage例項的時候就寫入預設值,因為預設值可以作為執行異常的備份,也可以用作頁面的單元測試。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70011554/viewspace-2989448/,如需轉載,請註明出處,否則將追究法律責任。

相關文章