【鸿蒙开发】第十章 ArkTS语言UI范式-状态管理(二)

26 篇文章 5 订阅
26 篇文章 4 订阅

1 前言

上章节第九章 ArkTS语言UI范式-状态管理(一)我们了解了状态管理是什么,分别有哪些状态管理,并介绍了组件内状态管理的相关知识,本章节接着上一章节的内容,我们来继续学习应用状态管理其他状态管理的相关知识。

2 应用状态的装饰器

上一个章节中介绍的装饰器仅能在页面内,即一个组件树上共享状态变量。如果开发者要实现应用级的,或者多个页面的状态数据共享,就需要用到应用级别的状态管理的概念。ArkTS根据不同特性,提供了多种应用状态管理的能力:

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

2.1 LocalStorage:页面级UI状态存储

LocalStorage页面级UI状态存储,通过@Entry装饰器接收的参数可以在页面内共享同一个LocalStorage实例LocalStorage支持UIAbility实例内多个页面间状态共享。
LocalStorage使用场景和相关的装饰器:单向同步@LocalStorageProp双向同步@LocalStorageLink

  1. @LocalStorageProp:@LocalStorageProp装饰的变量和与LocalStorage中给定属性建立单向同步关系
  2. @LocalStorageLink:@LocalStorageLink装饰的变量和在@Component中创建与LocalStorage中给定属性建立双向同步关系

如果要建立LocalStorage自定义组件的联系,需要使用@LocalStorageProp@LocalStorageLink装饰器。使用@LocalStorageProp(key)/@LocalStorageLink(key)装饰组件内的变量,key标识了LocalStorage的属性。

当自定义组件初始化的时候,@LocalStorageProp(key)/@LocalStorageLink(key)装饰的变量会通过给定的key,绑定LocalStorage对应的属性,完成初始化。本地初始化是必要的,因为无法保证LocalStorage一定存在给定的key(这取决于应用逻辑是否在组件初始化之前在LocalStorage实例中存入对应的属性)。

2.1.1 @LocalStorageProp

@LocalStorageProp(key)是和LocalStoragekey对应的属性建立单向数据同步,我们允许本地改变的发生,但是对于@LocalStorageProp,本地的修改永远不会同步回LocalStorage中,相反,如果LocalStorage给定key的属性发生改变,改变会被同步给@LocalStorageProp,并覆盖掉本地的修改。

在这里插入图片描述
变量的传递/访问规则说明
在这里插入图片描述
在这里插入图片描述

2.1.2 @LocalStorageLink

如果我们需要将自定义组件的状态变量的更新同步回LocalStorage,就需要用到@LocalStorageLink

@LocalStorageLink(key)是和LocalStoragekey对应的属性建立双向数据同步:

本地修改发生,该修改会被写回LocalStorage中;

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

在这里插入图片描述

变量的传递/访问规则说明
在这里插入图片描述

在这里插入图片描述

2.1.3 应用逻辑使用

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

2.1.4 UI内部使用

@LocalStorageProp实现单向同步

// 创建新实例并使用给定对象初始化
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实例
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') 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()}`)

      // 注意:这里没有同步刷新,因为storage.get<number>(‘PropA’)返回的是常规变量,常规变量的更新并不会引起Text组件的重新渲染。
      Text(`playCount in LocalStorage for debug ${storage.get<number>('PropA')}`)
        .width(300).height(60).fontSize(12)
    }
  }
}

2.1.5 多个视图中同享

如果希望LocalStorage的实例在多个视图中共享我们应该如何操作呢?

  1. 创建:可以在所属UIAbility中创建LocalStorage实例,并调用windowStage.loadContent
  2. 获取:在UI页面通过getShared接口获取在通过loadContent共享的LocalStorage实例。LocalStorage.getShared只在模拟器或者实机上才有效,不能在Preview预览器中使用。
import UIAbility from '@ohos.app.ability.UIAbility';
import window from '@ohos.window';
let param:Record<string,number> = { 'PropA': 47 };
let localStorage: LocalStorage = new LocalStorage(param);
export default class EntryAbility extends UIAbility {
  storage: LocalStorage = localStorage

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


// 通过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实例的时候就写入默认值,因为默认值可以作为运行异常的备份,也可以用作页面的单元测试。

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

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

  1. LocalStorage页面级,通常应用于页面内的数据共享。
  2. AppStorage应用级的全局状态共享,还相当于整个应用的“中枢”,持久化数据PersistentStorage和环境变量Environment都是通过AppStorage中转,才可以和UI交互。

下面我们来学习AppStorage使用场景和相关的装饰器:@StorageProp@StorageLink。区别类比上面LocalStorage的装饰器@LocalStorageProp@LocalStorageLink,其中Prop为单向同步Link为双向同步

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

2.2.1 从应用逻辑使用AppStorage和LocalStorage

AppStorage单例,它的所有API都是静态的,使用类似LocalStorage

// 1.LocalStorage的基本使用
let storage: LocalStorage = new LocalStorage();
storage.setOrCreate('PropA',17);

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


// 2.AppStorage的基本使用
AppStorage.setOrCreate('PropA', 47);
AppStorage.get<number>('PropA') // == 49

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

link1.get() // == 49
link2.get() // == 49
prop.get() // == 49

2.2.2 从UI内部使用AppStorage和LocalStorage

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


// 1.LocalStorage配合LocalStorageLink使用
let storage = new LocalStorage();
storage.setOrCreate('PropA',48);

// 2.AppStorage配合StorageLink使用
AppStorage.setOrCreate('PropA', 47);


@Entry(storage)
@Component
struct CompA {

  // 对应LocalStorage中的数据
  @LocalStorageLink('PropA') localStorLink: number = 1;

  // 对应AppStorage中的数据
  @StorageLink('PropA') storLink: number = 1;

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

2.2.3 事件通知

使用@StorageLinkAppStorage的双向同步的机制来实现事件通知时要注意,不可多个页面绑定同一变量,因为AppStorage中的变量绑定在多个不同页面的组件中,事件通知则不一定需要通知到所有的这些组件。并且,当这些@StorageLink装饰的变量在UI中使用时,会触发UI刷新,带来不必要的性能影响。
非要使用需要注意这两点:

  1. 是否需要多个页面绑定一个变量;
  2. 是否需要在UI中使用来刷新UI。
class ViewData {
  title: string;
  uri: Resource;
  color: Color = Color.Black;

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

@Component
struct Gallery {

  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 {
  // 全局变量,记录当前选择的index
  @StorageLink('tapIndex') tapIndex: number = -1;
  private index: number
  private uri: Resource

  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
        })
    }
  }
}

相比借助@StorageLink的双向同步机制实现事件通知,可以使用emit订阅某个事件并接收事件回调的方式来减少开销,增强代码的可读性。emitter的具体使用这里不展开,可到文档中查阅使用方式。

2.3 PersistentStorage:持久化存储UI状态

前面学习了解到LocalStorageAppStorage都是运行时的内存,但是在应用退出再次启动后,依然能保存选定的结果,是应用开发中十分常见的现象,这就需要用到PersistentStorage

PersistentStorage是应用程序中的可选单例对象。此对象的作用是持久化存储选定的AppStorage属性以确保这些属性在应用程序重新启动时的值与应用程序关闭时的值相同

2.3.1 概述

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

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

2.3.2 限制条件

PersistentStorage类型和值的限制:

  1. number, string, boolean, enum 等简单类型。
  2. 可以被JSON.stringify()和JSON.parse()重构的对象。例如Date, Map, Set等内置类型则不支持,以及对象的属性方法不支持持久化。
  3. 不支持嵌套对象(对象数组,对象的属性是对象等)。因为目前框架无法检测AppStorage中嵌套对象(包括数组)值的变化,所以无法写回到PersistentStorage中。
  4. 不支持undefined 和 null 。

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

  1. 持久化大型数据集。
  2. 持久化经常变化的变量。

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

PersistentStorageUIContext相关联,需要在UIContext明确的时候才可以调用,可以通过在runScopedTask里明确上下文。如果没有在UIContext明确的地方调用,将导致无法持久化数据。

2.3.3 实例

// 初始化PersistentStorage
PersistentStorage.PersistProp('aProp', 47);

@Entry
@Component
struct Index {
  @State message: string = 'Hello World'
  // 在组件内部定义
  @StorageLink('aProp') aProp: number = 48

  aboutToAppear() {
    // 在AppStorage获取对应属性
    var aProp = AppStorage.Get<number>('aProp'); // returns 47
  }

  build() {
    Row() {
      Column() {
        Text(this.message)
        // 应用退出时会保存当前结果。重新启动后,会显示上一次的保存结果
        Text(`${this.aProp}`)
          .onClick(() => {
            // @StorageLink装饰的变量是和AppStorage中建立双向同步的,变化会被同步回AppStorage中
            // 该属性已经被持久化,所以在AppStorage中“aProp”的改变会触发PersistentStorage,将新的改变写入本地磁盘。
            this.aProp += 1;
          })
      }
    }
  }
}

在这里插入图片描述
注意:在调用PersistentStorage.persistProp或者persistProps之前使用接口 AppStorage.setOrCreate去改变AppStorage中的属性是错误的,因为这样的调用顺序会丢失上一次应用程序运行中的属性值

2.4 Environment:设备环境查询

开发者如果需要应用程序运行的设备的环境参数,以此来作出不同的场景判断,比如多语言,暗黑模式等,需要用到Environment设备环境查询。

Environment是ArkUI框架在应用程序启动时创建的单例对象。它为AppStorage提供了一系列描述应用程序运行状态的属性。Environment的所有属性都是不可变的(即应用不可写入),所有的属性都是简单类型。
在这里插入图片描述

2.4.1 实例

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

@Entry
@Component
struct Index {

  // StorageProp从AppStorage获取单向绑定的languageCode的变量
  @StorageProp('languageCode') languageCode: string = 'en';

  aboutToAppear() {
    // 从AppStorage获取单向绑定的languageCode的变量
    var lang: SubscribedAbstractProperty<string> = AppStorage.Prop('languageCode');
    if (lang.get() === 'zh') {
      console.info('你好');
    } else {
      console.info('Hello!');
    }
  }

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

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

3 其他状态管理

除了前面章节提到的组件状态管理和应用状态管理,ArkTS还提供了@Watch$$来为开发者提供更多功能:

  1. @Watch用于监听状态变量的变化。
  2. $$运算符:给内置组件提供TS变量的引用,使得TS变量和内置组件的内部状态保持同步。

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

@Watch应用于对状态变量的监听。如果开发者需要关注某个状态变量的值是否改变,可以使用@Watch为状态变量设置回调函数@WatchArkUI框架内部判断数值有无更新使用的是严格相等(===),遵循严格相等规范。当在严格相等为false的情况下,就会触发@Watch的回调

3.1.1 装饰器说明

在这里插入图片描述
使用及注意:

  1. 当观察到状态变量的变化(包括双向绑定的AppStorageLocalStorage中对应的key发生的变化)的时候,对应的@Watch的回调方法将被触发
  2. @Watch方法在自定义组件的属性变更之后同步执行;
  3. 如果在@Watch的方法里改变了其他的状态变量,也会引起状态变更和@Watch的执行;
  4. 在第一次初始化的时候,@Watch装饰的方法不会被调用,即认为初始化不是状态变量的改变。只有在后续状态改变时,才会调用@Watch回调方法。
  5. 注意避免无限循环。循环可能是因为在@Watch的回调方法里直接或者间接地修改了同一个状态变量引起的。为了避免循环的产生,建议不要在@Watch的回调方法里修改当前装饰的状态变量;
  6. 注意关注性能,属性值更新函数会延迟组件的重新渲染(具体请见上面的行为表现),因此,回调函数应仅执行快速运算;
  7. 不建议在@Watch函数中调用async await,因为@Watch设计的用途是为了快速的计算,异步行为可能会导致重新渲染速度的性能问题。

3.1.2 实例

@Entry
@Component
struct TotalView {
  // Watch修饰的变量发生改变,会回调到onCountUpdated方法中
  @Prop @Watch('onCountUpdated') count: number = 0;
  @State total: number = 0;

  // @Watch 回调
  onCountUpdated(propName: string): void {
    // propName为变量名count,值为this.count
    console.log(`${propName}:${this.count}`)
    this.total += this.count;
  }

  build() {
    Column() {
      // total为count的值
      Text(`Total: ${this.total}`)
      Button('count++')
        .onClick(() => {
          // 点击按钮count+1
          this.count++
        })
    }
  }
}

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

$$运算符为系统内置组件提供TS变量的引用,使得TS变量系统内置组件的内部状态保持同步。内部状态具体指什么取决于组件。例如,TextInput组件的text参数

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

当前$$支持的组件
在这里插入图片描述

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

3.2.1 实例

@Entry
@Component
struct TextInputExample {
  @State text: string = ''
  controller: TextInputController = new TextInputController()

  build() {
    Column({ space: 20 }) {
      Text(this.text)
      // $$绑定的变量变化
      TextInput({ text: $$this.text, placeholder: 'input your word...', controller: this.controller })
        .placeholderColor(Color.Grey)
        .placeholderFont({ size: 14, weight: 400 })
        .caretColor(Color.Blue)
        .width(300)
    }.width('100%').height('100%').justifyContent(FlexAlign.Center)
  }
}

4 实践

4.1使用@ObjectLink代替@Prop减少不必要的深拷贝

在应用开发中,开发者经常会进行父子组件的数值传递,而在不会改变子组件内状态变量值的情况下,使用@Prop装饰状态变量会导致组件创建的耗时增加,从而影响一部分性能。

【反例】

@Observed
class ClassA {
  public c: number = 0;

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

@Component
struct PropChild {
  @Prop testNum: ClassA; // @Prop 装饰状态变量会深拷贝

  build() {
    Text(`PropChild testNum ${this.testNum.c}`)
  }
}

@Entry
@Component
struct Parent {
  @State testNum: ClassA[] = [new ClassA(1)];

  build() {
    Column() {
      Text(`Parent testNum ${this.testNum[0].c}`)
        .onClick(() => {
          this.testNum[0].c += 1;
        })
      // PropChild没有改变@Prop testNum: ClassA的值,所以这时最优的选择是使用@ObjectLink
      PropChild({ testNum: this.testNum[0] })
    }
  }
}

在上文的示例中,PropChild组件没有改变@Prop testNum: ClassA的值,所以这时较优的选择是使用@ObjectLink,因为@Prop会深拷贝数据,具有拷贝的性能开销,所以这个时候@ObjectLink是比@Link@Prop更优的选择。

【正例】

@Observed
class ClassA {
  public c: number = 0;

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

@Component
struct PropChild {
  @ObjectLink testNum: ClassA; // @ObjectLink 装饰状态变量不会深拷贝

  build() {
    Text(`PropChild testNum ${this.testNum.c}`)
  }
}

@Entry
@Component
struct Parent {
  @State testNum: ClassA[] = [new ClassA(1)];

  build() {
    Column() {
      Text(`Parent testNum ${this.testNum[0].c}`)
        .onClick(() => {
          this.testNum[0].c += 1;
        })
      PropChild({ testNum: this.testNum[0] })
    }
  }
}

4.2 不使用状态变量强行更新非状态变量关联组件

【反例】

@Entry
@Component
struct CompA {
  @State needsUpdate: boolean = true;
  realState1: Array<number> = [4, 1, 3, 2]; // 未使用状态变量装饰器
  realState2: Color = Color.Yellow;

  updateUI1(param: Array<number>): Array<number> {
    const triggerAGet = this.needsUpdate;
    return param;
  }
  updateUI2(param: Color): Color {
    const triggerAGet = this.needsUpdate;
    return param;
  }
  build() {
    Column({ space: 20 }) {
      ForEach(this.updateUI1(this.realState1),
        (item: Array<number>) => {
          Text(`${item}`)
        })
      Text("add item")
        .onClick(() => {
          // 改变realState1不会触发UI视图更新
          this.realState1.push(this.realState1[this.realState1.length-1] + 1);

          // 触发UI视图更新
          this.needsUpdate = !this.needsUpdate;
        })
      Text("chg color")
        .onClick(() => {
          // 改变realState2不会触发UI视图更新
          this.realState2 = this.realState2 == Color.Yellow ? Color.Red : Color.Yellow;

          // 触发UI视图更新
          this.needsUpdate = !this.needsUpdate;
        })
    }.backgroundColor(this.updateUI2(this.realState2))
    .width(200).height(500)
  }
}

上述示例存在以下问题:

  1. 应用程序希望控制UI更新逻辑,但在ArkUI中,UI更新的逻辑应该是由框架来检测应用程序状态变量的更改去实现。

  2. this.needsUpdate是一个自定义的UI状态变量,应该仅应用于其绑定的UI组件。变量this.realState1、this.realState2没有被装饰,他们的变化将不会触发UI刷新。

但是在该应用中,用户试图通过this.needsUpdate的更新来带动常规变量this.realState1this.realState2的更新,此方法不合理更新性能较差

【正例】
要解决此问题,应将realState1realState2成员变量用@State装饰。一旦完成此操作,就不再需要变量needsUpdate

@Entry
@Component
struct CompA {
  @State realState1: Array<number> = [4, 1, 3, 2];
  @State realState2: Color = Color.Yellow;
  build() {
    Column({ space: 20 }) {
      ForEach(this.realState1,
        (item: Array<number>) => {
          Text(`${item}`)
        })
      Text("add item")
        .onClick(() => {
          // 改变realState1触发UI视图更新
          this.realState1.push(this.realState1[this.realState1.length-1] + 1);
        })
      Text("chg color")
        .onClick(() => {
          // 改变realState2触发UI视图更新
          this.realState2 = this.realState2 == Color.Yellow ? Color.Red : Color.Yellow;
        })
    }.backgroundColor(this.realState2)
    .width(200).height(500)
  }
}

4.3精准控制状态变量关联的组件数

精准控制状态变量关联的组件数能减少不必要的组件刷新,提高组件的刷新效率。有时开发者会将同一个状态变量绑定多个同级组件的属性,当状态变量改变时,会让这些组件做出相同的改变,这有时会造成组件的不必要刷新,如果存在某些比较复杂的组件,则会大大影响整体的性能。但是如果将这个状态变量绑定在这些同级组件的父组件上,则可以减少需要刷新的组件数,从而提高刷新的性能。

【反例】

@Observed
class Translate {
  translateX: number = 20;
}
@Component
struct Title {
  @ObjectLink translateObj: Translate;
  build() {
    Row() {
      Image($r('app.media.icon'))
        .width(50)
        .height(50)
        .translate({
          x:this.translateObj.translateX // this.translateObj.translateX used in two component both in Row
        })
      Text("Title")
        .fontSize(20)
        .translate({
          x: this.translateObj.translateX
        })
    }
  }
}
@Entry
@Component
struct Page {
  @State translateObj: Translate = new Translate();
  build() {
    Column() {
      Title({
        translateObj: this.translateObj
      })
      Stack() {
      }
      .backgroundColor("black")
      .width(200)
      .height(400)
      .translate({
        x:this.translateObj.translateX //this.translateObj.translateX used in two components both in Column
      })
      Button("move")
        .translate({
          x:this.translateObj.translateX
        })
        .onClick(() => {
          animateTo({
            duration: 50
          },()=>{
            this.translateObj.translateX = (this.translateObj.translateX + 50) % 150
          })
        })
    }
  }
}

在上面的示例中,状态变量this.translateObj.translateX被用在多个同级的子组件下,当this.translateObj.translateX变化时,会导致所有关联它的组件一起刷新,但实际上由于这些组件的变化是相同的,因此可以将这个属性绑定到他们共同的父组件上,来实现减少组件的刷新数量。经过分析,所有的子组件其实都处于Page下的Column中,因此将所有子组件相同的translate属性统一到Column上,来实现精准控制状态变量关联的组件数。

【正例】

@Observed
class Translate {
  translateX: number = 20;
}
@Component
struct Title {
  build() {
    Row() {
      Image($r('app.media.icon'))
        .width(50)
        .height(50)
      Text("Title")
        .fontSize(20)
    }
  }
}
@Entry
@Component
struct Page1 {
  @State translateObj: Translate = new Translate();
  build() {
    Column() {
      Title()
      Stack() {
      }
      .backgroundColor("black")
      .width(200)
      .height(400)
      Button("move")
        .onClick(() => {
          animateTo({
            duration: 50
          },()=>{
            this.translateObj.translateX = (this.translateObj.translateX + 50) % 150
          })
        })
    }
    .translate({ // the component in Column shares the same property translate
      x: this.translateObj.translateX
    })
  }
}

参考文献:
[1]OpenHarmoney应用开发文档

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

__Yvan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值