简述鸿蒙开发中组件的状态管理

在应用中,界面通常都是动态的。

如下图所示,在列表中,当用户点击目标一,目标一会呈现展开状态,再次点击目标一,目标一呈现收起状态。

界面会根据不同的状态展示不一样的效果。这跟使用 Vue 等前端框架开发 web 应用是一样的,即数据驱动视图

简述鸿蒙开发中组件的状态管理

ArkUI 作为一种声明式UI,具有状态驱动UI更新的特点。

在声明式UI编程框架中,UI是程序状态的运行结果,用户构建了一个UI模型,其中应用的运行时的状态是参数。当参数改变时,UI作为返回结果,也将进行对应的改变。

这些运行时的状态变化所带来的UI的重新渲染,在 ArkUI 中统称为状态管理机制

当用户进行界面交互或有外部事件引起状态改变时,状态的变化会触发组件自动更新。

所以,在 ArkUI 中,只需要通过一个变量来记录状态,当改变状态的时候,ArkUI 就会自动更新界面中受影响的部分。

简述鸿蒙开发中组件的状态管理

理清状态的几种基本概念

  • 状态变量:被状态装饰器装饰的变量,状态变量值的改变会引起UI的渲染更新。示例:@State num: number = 1,其中,@State是状态装饰器,num是状态变量。

  • 常规变量:没有被状态装饰器装饰的变量,通常应用于辅助计算。它的改变永远不会引起UI的刷新。以下示例中increaseBy变量为常规变量。

  • 数据源/同步源:状态变量的原始来源,可以同步给不同的状态数据。通常意义为父组件传给子组件的数据。以下示例中数据源为count: 1。

  • 命名参数机制父组件通过指定参数传递给子组件的状态变量,为父子传递同步参数的主要手段。示例:CompA: ({ aProp: this.aProp })。

  • 从父组件初始化:父组件使用命名参数机制,将指定参数传递给子组件。子组件初始化的默认值在有父组件传值的情况下,会被覆盖。示例:

@Component
struct MyComponent {
  @State count: number = 0;
  private increaseBy: number = 1;

  build() {
  }
}

@Component
struct Parent {
  build() {
    Column() {
      // 从父组件初始化,覆盖本地定义的默认值
      MyComponent({ count: 1, increaseBy: 2 })
    }
  }
}
  • 初始化子节点:父组件中状态变量可以传递给子组件,初始化子组件对应的状态变量。示例同上。

  • 本地初始化:在变量声明的时候赋值,作为变量的默认值。示例:@State count: number = 0。

装饰器总览

ArkUI 框架提供了多种管理状态的装饰器来修饰变量,使用这些装饰器修饰的变量即称为状态变量。

变量必须被装饰器装饰才可以成为状态变量,如果不使用状态变量,UI只能在初始化时渲染,后续将不会再刷新。

ArkUI提供了多种装饰器,通过使用这些装饰器,状态变量不仅可以观察在组件内的改变,还可以在不同组件层级间传递,比如父子组件、跨组件层级,也可以观察全局范围内的变化。根据状态变量的影响范围,将所有的装饰器可以大致分为:

  • 管理组件拥有状态的装饰器:组件级别的状态管理,可以观察组件内变化,和不同组件层级的变化,但需要唯一观察同一个组件树上,即同一个页面内。

  • 管理应用拥有状态的装饰器:应用级别的状态管理,可以观察不同页面,甚至不同 UIAbility的状态变化,是应用内全局的状态管理。

从数据的传递形式和同步类型层面看,装饰器也可分为:

  • 只读的单向传递

  • 可变更的双向传递

简述鸿蒙开发中组件的状态管理

上图中,Components 部分的装饰器为组件级别的状态管理,Application 部分为应用的状态管理。开发者可以通过@StorageLink/@LocalStorageLink实现应用和组件状态的双向同步,通过@StorageProp/@LocalStorageProp实现应用和组件状态的单向同步。

下面将着重介绍管理组件拥有的状态,应用拥有的状态将在后续的文章中介绍。

管理组件拥有的状态

在组件范围传递的状态管理常见的场景如下:

简述鸿蒙开发中组件的状态管理

  • @State:@State装饰的变量拥有其所属组件的状态,可以作为其子组件单向和双向同步的数据源。当其数值改变时,会引起相关组件的渲染刷新。

  • @Prop:@Prop装饰的变量可以和父组件建立单向同步关系,@Prop装饰的变量是可变的,但修改不会同步回父组件

  • @Link:@Link装饰的变量和父组件构建双向同步关系的状态变量,父组件会接受来自@Link装饰的变量的修改的同步,父组件的更新也会同步给@Link装饰的变量。

  • @Provide/@Consume:@Provide/@Consume装饰的变量用于跨组件层级(多层组件)同步状态变量,可以不需要通过参数命名机制传递,通过alias(别名)或者属性名绑定。

@Observed 和 @ObjectLink 主要是应用于多层嵌套的情况,比如二维数组,或者数组项class,或者class的属性是class,他们的第二层的属性变化是无法观察到的。后面会介绍。

@State

当需要在组件内使用状态来控制UI的不同呈现方式时,可以使用@State装饰器。以下图为例,当点击子目标列表的其中一项,列表项会展开。当再次点击同一项,列表项会收起。所以,对于某一个列表项来说,它的呈现方式会受列表项是否展开这个状态影响。

简述鸿蒙开发中组件的状态管理

将是否展开这个状态定义为isExpanded变量,当其值为false表示目标项收起,值为true时表示目标项展开。

此时,需要使用@State装饰器修饰isExpanded,使其成为目标项内部的状态变量。通过@State装饰后,框架内部会建立数据与视图间的绑定,

简述鸿蒙开发中组件的状态管理

最后,通过条件渲染,实现是否显示进度调整面板和列表项的高度变化。

@Component
export default struct TargetListItem {
  @State isExpanded: boolean = false;
  ...

  build() {
    ...
      Column() {
        ...
        if (this.isExpanded) {
          Blank()
          ProgressEditPanel(...)
        }
      }
      .height(this.isExpanded ? $r('app.float.expanded_item_height')                  
      : $r('app.float.list_item_height'))
      .onClick(() => {
        ...
             this.isExpanded = !this.isExpanded;
        ...
       })
    ...
  }
}

@Prop

当子组件中的状态依赖从父组件传递而来时,需要使用@Prop装饰器,@Prop修饰的变量可以和其父组件中的状态建立单向同步关系。当父组件中状态变化时,该状态值也会更新至@Prop修饰的变量;

注意:对@Prop修饰的变量的修改不会影响其父组件中的状态

父组件 TargetList:

@Component
export default struct TargetList {
  // 父组件声明了一个isEditMode变量
  @State isEditMode: boolean = false;
  ...
  build() {
    Column() {
      Row() {
        ...
        Text($r('app.string.cancel_button'))
          .onClick(() => {
            // 父组件更新isEditMode
            this.isEditMode = false;
            ...
           })
           ...
      }
      ...
      List({ space: CommonConstants.LIST_SPACE }) {
        ForEach(this.targetData, (item: TaskItemBean, index: number) => {
          ListItem() {
            TargetListItem({
              // 传递给子组件TargetListItem
              isEditMode: this.isEditMode,
              ...
            })
          }
        }, (item, index) => JSON.stringify(item) + index)
      }
    }
  }
}

父组件 TargetListItem:

@Component
export default struct TargetListItem {
   @Prop isEditMode: boolean;
   ...
       Column() {
        ...
       }
       .padding({
        ...
        // 根据父组件传递的isEditMode显示不同的样式
        right: this.isEditMode ? $r('app.float.list_edit_padding') 
               : $r('app.float.list_padding')
       })
       ...

       if (this.isEditMode) {
        Row() {
           Checkbox()...
        }
       }
  ...
}

即,从父组件单向同步isEditMode状态:

简述鸿蒙开发中组件的状态管理

@Link

若是父子组件状态需要相互绑定进行双向同步时(类似于 vue 中的v-model),可以使用@Link装饰器。

父组件中用于初始化子组件@Link变量的必须是在父组件中定义的状态变量。

以下面这个例子为例:

简述鸿蒙开发中组件的状态管理

当用户点击同一个目标,目标项会展开或者收起。当用户点击不同的目标项时,除了被点击的目标项展开,同时前一次被点击的目标项会收起。

从目标一切换到目标三的流程中,关键在于最后目标一的收起,当点击目标三时,目标一需要知道点击了目标三,目标一才会收起。

在子目标列表中,每个列表项都有其位置索引值index属性,表示目标项在列表中的位置。index从0开始,即第一个目标项的索引值为0,第二个目标项的索引值为1,以此类推。此外,clickIndex用来记录被点击的目标项索引。当点击目标一时,clickIndex为0,点击目标三时,clickIndex为2。

在父组件子目标列表和每个子组件目标项中都拥有clickIndex状态。当目标一展开时,clickIndex为0。此时点击目标三,目标三的clickIndex变为2,只要其父组件子目标列表感知到clickIndex状态变化,同时将此变化传递给目标一。目标一的clickIndex即可同步改变为2,即目标一感知到此时点击了目标三。

简述鸿蒙开发中组件的状态管理

首先,需要在父组件TargetList中定义clickIndex状态。

在父组件中使用子组件时,将父组件的clickIndex传递给子组件的clickIndex。其中父组件的clickIndex加上$表示传递的是引用。

@Component
export default struct TargetList {
  @State clickIndex: number = CommonConstants.DEFAULT_CLICK_INDEX;
  ...
             TargetListItem({
               // $ 表示引用
               clickIndex: $clickIndex,
              ...
             })
  ...
}

然后,在子组件TargetListItem中用@Link装饰器定义clickIndex,当点击目标项时,clickIndex更新为当前目标索引值。

@Component
export default struct TargetListItem {
  // @Watch('onClickIndexChanged')表示当clickIndex改变时,执行onClickIndexChanged方法
  @Link @Watch('onClickIndexChanged') clickIndex: number;
  @State isExpanded: boolean = false
  ...

  onClickIndexChanged() {
    if (this.clickIndex != this.index) {
      this.isExpanded = false;
    }
  }

  build() {
    ...
       Column() {
        ...
       }
       .onClick(() => {
        ...
           this.clickIndex = this.index;
        ...
       })
    ...
  }
}

@Provide和@Consume

简述鸿蒙开发中组件的状态管理

跨组件层级双向同步状态是指 @Provide 修饰的状态变量自动对提供者组件的所有后代组件可用,后代组件通过使用 @Consume 装饰的变量来获得对提供的状态变量的访问。

使用@Provide的好处是开发者不需要多次将变量在组件间传递。

  • @Provide作为数据的提供方,可以更新其子孙节点的数据,并触发页面渲染。
  • @Consume在感知到@Provide数据的更新后,会触发当前自定义组件的重新渲染。

如果你有 vue 开发的经验,理解这个是比较容易的。

总结

  • ArkUI 作为一种声明式UI,具有状态驱动UI更新的特点。

  • @State: 组件内的状态管理

  • @Prop: 从父组件单向同步状态

  • @Link: 与父组件双向同步状态

  • @Provide和@Consume: 跨组件层级双向同步状态

原文链接:https://juejin.cn/post/7315846799083896883 作者:小p

(0)
上一篇 2023年12月24日 下午4:57
下一篇 2023年12月24日 下午5:07

相关推荐

发表回复

登录后才能评论