OpenHarmony状态管理

举报
坚果的博客 发表于 2022/08/05 13:04:25 2022/08/05
【摘要】 状态管理响应式的编程框架中都会有一个永恒的主题——“状态(State)管理”,无论是在 React/Vue(两者都是支持响应式编程的 Web 开发框架),还是 AarkUI开发框架都提供了多维度的状态管理机制,和UI相关联的数据,不仅可以在组件内使用,还可以在不同组件层级间传递,比如父子组件之间,爷孙组件之间等,也可以是全局范围内的传递,还可以是跨设备传递。另外,从数据的传递形式来看,可以分...

状态管理

响应式的编程框架中都会有一个永恒的主题——“状态(State)管理”,无论是在 React/Vue(两者都是支持响应式编程的 Web 开发框架),还是 AarkUI开发框架都提供了多维度的状态管理机制,和UI相关联的数据,不仅可以在组件内使用,还可以在不同组件层级间传递,比如父子组件之间,爷孙组件之间等,也可以是全局范围内的传递,还可以是跨设备传递。另外,从数据的传递形式来看,可以分为只读的单向传递和可变更的双向传递。如下图所示,开发框架提供了多种应用程序状态管理的能力。那么在使用的时候如何选择呢?答案是取决于实际情况!以下是管理状态的最常见的方法:

  • Widget 管理自己的状态。

  • Widget 管理子 Widget 状态。

  • 混合管理(父 Widget 和子 Widget 都管理状态)。

  • 跨组件管理状态,爷孙组件

如何决定使用哪种管理方法?下面是官方给出的一些原则可以帮助你做决定:

  • 如果状态是用户数据,如复选框的选中状态、滑块的位置,则该状态最好由父 Widget 管理。

  • 如果状态是有关界面外观效果的,例如颜色、动画,那么状态最好由 Widget 本身来管理。

  • 如果某一个状态是不同 Widget 共享的则最好由它们共同的父 Widget 管理。

在 Widget 内部管理状态封装性会好一些,而在父 Widget 中管理会比较灵活。有些时候,如果不确定到底该怎么管理状态,那么推荐的首选是在父 Widget 中管理(灵活会显得更重要一些)。



状态变量装饰器的作用

管理组件内部的状态数据,有指向性的更新目标视图。

  • @State: 组件拥有的状态属性,当@State装饰的变量更改时,组件会重新渲染更新视图。

  • @Prop: 依赖父组件的状态属性,但是子组件所做的更改不会引起到父组件的视图刷新,属于单向传递。

  • @Link: 和@Prop相似,但是不同之处在于,当任何一个组件中的数据更新时,另一个组件的状态都会更新,父子组件重新渲染。


三种状态变量装饰器的异同点

这三种装饰器都有各自的特点,主要总结如下:

相同点:只能在组件内使用。

异同点:1)@Prop只支持基本类型number,string,boolean( 注意点:必须小写 ), @State@Link除了支持上述的类型外,还支持上述类型的数组,但是不支持复杂类型object和any( *注意点*:官网介绍的不支持,但是实际用是可以支持的( 除了@State的number类型 ),);


2)@State组件内不同实例的状态数据是独立的;

3)初始化数据方式不同,@State可以在自己的组件内初始化,也可以为自定义的组件提供传值的方式进行初始化;@Prop@Link初始化则是父组件里面@State定义的变量( 注意点:不能在组件内部初始化,只能定义类型;

4)@Prop是单向,内部修改不会更新父组件);@Link则是双向,会通知父组件@State变量。

5)如果是多层嵌套(也就是子组件里面再嵌套孙子组件),那里面所有的组件定义的状态名必须保持和父级组件的传值参数名一致 。

@State修饰符

@State 装饰的变量是组件内部的状态数据,当这些状态数据被修改时,将会调用所在组件的 build() 方法刷新UI。 @State 状态数据具有以下特征:

  • 支持多种数据类型:允许 classnumberbooleanstring 强类型的按值和按引用类型。允许这些强类型构成的数组,即Array<class>Array<string>Array<boolean>Array<number>。不允许 objectany

  • 内部私有:标记为 @State 的属性是私有变量,只能在组件内访问。

  • 支持多个实例:组件不同实例的内部状态数据独立。

  • 需要本地初始化:必须为所有 @State 变量分配初始值,将变量保持未初始化可能导致框架行为未定义,初始值需要是有意义的值,比如设置 class 类型的值为 null 就是无意义的,会导致编译报错。

  • 创建自定义组件时支持通过状态变量名设置初始值:在创建组件实例时,可以通过变量名显式指定 @State 状态属性的初始值。

    简单样例如下所示:

    /*
     * Copyright (c) 2021 JianGuo Device Co., Ltd.
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *    http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    @Entry
    @Component
    struct ComponentTest {
      @State content: string = "父组件"; // data变化会触发build方法执行
    ​
      build() {
        Column({ space: 10 }) {
    ​
          Text(`${this.content}`)
            .fontSize(36)
    ​
    ​
          Item() // 子组件
          Item() // 子组件
    ​
          Button('更新内容')
            .onClick(() => {
              this.content = "修改为新内容"; // 点击按钮,date变化,会触发build方法执行
            })
        }
        .width('100%')
        .height('100%')
        .padding(10)
      }
    }
    ​
    // 自定义子组件
    @Component
    struct Item {
      @State content: string = "子组件" ;
    ​
      build() {
        Text(`${this.content}`)
          .fontSize(36)
    ​
          .onClick(() => {
            this.content = "修改为新内容"; // 点击更新内容,执行build方法
          })
      }
    }


    样例运行结果如下图所示:

    gif16

@Prop修饰符

@Prop@Stat 有相同的语义,但初始化方式不同, @Prop 装饰的变量可以和父组件的 @State 变量建立单向的数据绑定。即 @Prop 修饰的变量必须使用其父组件提供的 @State 变量进行初始化,允许组件内部修改 @Prop 变量值但更改不会通知给父组件。 @State 状态数据具有以下特征:

  • 支持简单数据类型:仅支持 numberstringboolean 简单类型;

  • 内部私有:标记为 @Prop 的属性是私有变量,只能在组件内访问。

  • 支持多个实例:组件不同实例的内部状态数据独立。

  • 不支持内部初始化:在创建组件的新实例时,必须将值传递给 @Prop 修饰的变量进行初始化,不支持在组件内部进行初始化。

    简单样例如下所示:

    /*
     * Copyright (c) 2021 JianGuo Device Co., Ltd.
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *    http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    ​
    /*
     * Copyright (c) 2021 JianGuo Device Co., Ltd.
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *    http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    @Entry
    @Component
    struct ComponentTest {
      @State content: string = "父组件"; // data变化会触发build方法执行
    ​
      build() {
        Column({ space: 10 }) {
    ​
          Text(`${this.content}`)
            .fontSize(36)
    ​
    ​
          Item({ content:this.content}) // 子组件
          Item({ content:this.content}) // 子组件
    ​
          Button('更新内容')
            .onClick(() => {
              this.content = "修改为新内容"; // 点击按钮,date变化,会触发build方法执行
            })
        }
        .width('100%')
        .height('100%')
        .padding(10)
      }
    }
    ​
    // 自定义子组件
    @Component
    struct Item {
      @Prop content: string  ;// // 不允许本地初始化
    ​
      build() {
        Text(`${this.content}`)
          .fontSize(36)
    ​
          .onClick(() => {
            this.content = "修改为新内容"; // 点击更新内容,执行build方法
          })
      }
    }


    样例运行结果如下图所示:

    gif

@Link修饰符

@Link@State 有相同的语义,但初始化方式不同, @Link 装饰的变量可以和父组件的 @State 变量建立双向的数据绑定。即 @Link 修饰的变量必须使用其父组件提供的 @State 变量进行初始化,允许组件内部修改 @Link 变量值且更改会通知给父组件。 @Link 状态数据具有以下特征:

  • 支持多种数据类型: @Link 变量的值与 @State 变量的类型相同,即 classnumberstringboolean 或这些类型的数组。

  • 内部私有:标记为 @Link 的属性是私有变量,只能在组件内访问。

  • 支持多个实例:组件不同实例的内部状态数据独立。

  • 不支持内部初始化:在创建组件的新实例时,必须将值传递给 @Prop 修饰的变量进行初始化,不支持在组件内部进行初始化。初始化使用 $ 符号,例如:$propertiesName。

样例如下:

/*
 * Copyright (c) 2021 JianGuo Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
​
​
​
@Entry
@Component
struct ComponentTest {
  @State content: string = "父组件"; // data变化会触发build方法执行
​
  build() {
    Column({ space: 10 }) {
​
      Text(`${this.content}`)
        .fontSize(36)
​
​
      Item({ content:$content}) // 子组件
      Item({ content:$content}) // 子组件
​
      Button('更新内容')
        .onClick(() => {
          this.content = "修改为新内容"; // 点击按钮,date变化,会触发build方法执行
        })
    }
    .width('100%')
    .height('100%')
    .padding(10)
  }
}
​
// 自定义子组件
@Component
struct Item {
  @Link content: string  ;// 变更time,父组件的对应属性也变化
​
​
  build() {
    Text(`${this.content}`)
      .fontSize(36)
​
      .onClick(() => {
        this.content = "修改新内容"; // 点击更新内容,执行build方法
      })
  }
}


样例运行结果如下图所示:

gif

@StorageLink修饰符

@StorageLink(key) 装饰的变量是组件内部的状态数据,当这些状态数据被修改时,将会调用所在组件的 build() 方法进行UI刷新。组件通过使用 @StorageLink(key) 装饰的状态变量与 AppStorage 建立双向数据绑定。当创建包含 @StorageLink 的状态变量的组件时,该状态变量的值将使用 AppStorage 中的值进行初始化,在UI组件中对 @StorageLink 的状态变量所做的更改将同步到 AppStorage ,并从 AppStorage 同步到任何其他绑定实例中,如 PersistentStorage 或其他绑定的UI组件。 @StorageLink 状态数据具有以下特征:

  • 支持多种数据类型:支持的数据类型和 @State 一致且支持 object

  • 需要本地初始化:必须为所有 @StorageLink 变量分配初始值。

  • 数据状态全局化:使用 @StorageLink 修饰的数据变化后全局都会改变。

  • 数据持久化:通过搭配 PersistentStorage 接口实现数据持久化。


    • 双向绑定数据

      简单样例如下所示:

      /*
       * Copyright (c) 2021 JianGuo Device Co., Ltd.
       * Licensed under the Apache License, Version 2.0 (the "License");
       * you may not use this file except in compliance with the License.
       * You may obtain a copy of the License at
       *
       *    http://www.apache.org/licenses/LICENSE-2.0
       *
       * Unless required by applicable law or agreed to in writing, software
       * distributed under the License is distributed on an "AS IS" BASIS,
       * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       * See the License for the specific language governing permissions and
       * limitations under the License.
       */
      /*
       * Copyright (c) 2021 JianGuo Device Co., Ltd.
       * Licensed under the Apache License, Version 2.0 (the "License");
       * you may not use this file except in compliance with the License.
       * You may obtain a copy of the License at
       *
       *    http://www.apache.org/licenses/LICENSE-2.0
       *
       * Unless required by applicable law or agreed to in writing, software
       * distributed under the License is distributed on an "AS IS" BASIS,
       * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       * See the License for the specific language governing permissions and
       * limitations under the License.
       */
      ​
      ​
      ​
      @Entry
      @Component
      struct ComponentTest {
        @StorageLink('content')  content: string = "父组件"; //  使用StorageLink标记并初始化
      ​
        build() {
          Column({ space: 10 }) {
      ​
            Text(`${this.content}`)
              .fontSize(36)
      ​
      ​
            Item1();
            Item1();
      ​
            Button('更新内容')
              .onClick(() => {
                this.content = "修改为新内容"; // 点击按钮,date变化,会触发build方法执行
              })
          }
          .width('100%')
          .height('100%')
          .padding(10)
        }
      }
      ​
      // 自定义子组件
      @Component
      struct Item1 {
      ​
        @StorageLink('content')  content: string ="子组件" ;// 变更time,父组件的对应属性也变化
      ​
      ​
        build() {
          Text(`${this.content}`)
            .fontSize(36)
      ​
            .onClick(() => {
              this.content = "修改新内容"; // 点击更新内容,执行build方法
            })
        }
      }


      运行结果如下图所示:

      gif

    • 页面间数据绑定

      简单样例如下图所示:

      /*
       * Copyright (c) 2021 JianGuo Device Co., Ltd.
       * Licensed under the Apache License, Version 2.0 (the "License");
       * you may not use this file except in compliance with the License.
       * You may obtain a copy of the License at
       *
       *    http://www.apache.org/licenses/LICENSE-2.0
       *
       * Unless required by applicable law or agreed to in writing, software
       * distributed under the License is distributed on an "AS IS" BASIS,
       * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       * See the License for the specific language governing permissions and
       * limitations under the License.
       */
      /*
       * Copyright (c) 2021 JianGuo Device Co., Ltd.
       * Licensed under the Apache License, Version 2.0 (the "License");
       * you may not use this file except in compliance with the License.
       * You may obtain a copy of the License at
       *
       *    http://www.apache.org/licenses/LICENSE-2.0
       *
       * Unless required by applicable law or agreed to in writing, software
       * distributed under the License is distributed on an "AS IS" BASIS,
       * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       * See the License for the specific language governing permissions and
       * limitations under the License.
       */
      ​
      import router from '@ohos.router';
      ​
      @Entry
      @Component
      struct ComponentTest {
        @StorageLink('content')  content: string = "父组件"; //  使用StorageLink标记并初始化
      ​
        build() {
          Column({ space: 10 }) {
      ​
            Text(`${this.content}`)
              .fontSize(36)
      ​
      ​
            Item1();
            Item1();
      ​
            Button('更新内容')
              .onClick(() => {
                this.content = "修改为新内容"; // 更改content的值,所有使用key的页面都会刷新
              })
            Button('跨页面更新数据')
              .onClick(() => {
                router.push({url: "pages/StorageLink2"})
              })
          }
          .width('100%')
          .height('100%')
          .padding(10)
        }
      }
      ​
      // 自定义子组件
      @Component
      struct Item1 {
      ​
        @StorageLink('content')  content: string ="子组件" ;// 变更time,父组件的对应属性也变化
      ​
      ​
        build() {
          Text(`${this.content}`)
            .fontSize(30)
      ​
            .onClick(() => {
              this.content = "我是第一个页面的子组件"; //更改content的值,所有使用key的页面都会刷新
            })
        }
      }




      第二个页面

      /*
       * Copyright (c) 2021 JianGuo Device Co., Ltd.
       * Licensed under the Apache License, Version 2.0 (the "License");
       * you may not use this file except in compliance with the License.
       * You may obtain a copy of the License at
       *
       *    http://www.apache.org/licenses/LICENSE-2.0
       *
       * Unless required by applicable law or agreed to in writing, software
       * distributed under the License is distributed on an "AS IS" BASIS,
       * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       * See the License for the specific language governing permissions and
       * limitations under the License.
       */
      import  router from '@ohos.router'
      // 第二个页面
      @Entry @Component struct Setting {
      ​
        @StorageLink('content') content: string = "我是第二个页面"; // content的值以'key'第一次出现的为准
      ​
        build() {
          Column({space: 10}) {
            Text(this.content) // tips的值以'key'第一次出现的为准
              .fontSize(20)
              .margin(20)
              .onClick(() => {
                this.content = "我是第二个页面的数据" // 更改content的值,所有使用key的页面都会更新
              })
      ​
            Button('返回')
              .onClick(() => {
                router.back()// 点击返回,首页的数据会更改
              })
          }
          .width('100%')
          .height('100%')
        }
      }

      运行结果如下图所示:

      8


    • 持久化数据

      @StorageLink 搭配 PersistentStorage 接口可以实现数据本地持久化,简单样例如下图所示:

      /*
       * Copyright (c) 2021 JianGuo Device Co., Ltd.
       * Licensed under the Apache License, Version 2.0 (the "License");
       * you may not use this file except in compliance with the License.
       * You may obtain a copy of the License at
       *
       *    http://www.apache.org/licenses/LICENSE-2.0
       *
       * Unless required by applicable law or agreed to in writing, software
       * distributed under the License is distributed on an "AS IS" BASIS,
       * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       * See the License for the specific language governing permissions and
       * limitations under the License.
       */
      /*
       * Copyright (c) 2021 JianGuo Device Co., Ltd.
       * Licensed under the Apache License, Version 2.0 (the "License");
       * you may not use this file except in compliance with the License.
       * You may obtain a copy of the License at
       *
       *    http://www.apache.org/licenses/LICENSE-2.0
       *
       * Unless required by applicable law or agreed to in writing, software
       * distributed under the License is distributed on an "AS IS" BASIS,
       * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       * See the License for the specific language governing permissions and
       * limitations under the License.
       */
      ​
      import router from '@ohos.router';
      // 持久化存储key并设置默认值
      PersistentStorage.PersistProp("content", "Hello, OpenHarmony")
      @Entry
      @Component
      struct ComponentTest {
        @StorageLink('content')  content: string = "父组件"; //  使用StorageLink标记并初始化
      ​
        build() {
          Column({ space: 10 }) {
      ​
            Text(`${this.content}`)
              .fontSize(36)
      ​
      ​
            Item1();
            Item1();
      ​
            Button('更新内容')
              .onClick(() => {
                this.content = "修改为新内容"; // 更改content的值,所有使用key的页面都会刷新
              })
            Button('跨页面更新数据')
              .onClick(() => {
                router.push({url: "pages/StorageLink2"})
              })
          }
          .width('100%')
          .height('100%')
          .padding(10)
        }
      }
      ​
      // 自定义子组件
      @Component
      struct Item1 {
      ​
        @StorageLink('content')  content: string ="子组件" ;// 变更time,父组件的对应属性也变化
      ​
      ​
        build() {
          Text(`${this.content}`)
            .fontSize(30)
      ​
            .onClick(() => {
              this.content = "我是第一个页面的子组件"; //更改content的值,所有使用key的页面都会刷新
            })
        }
      }


      运行结果如下图所示:

      9

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。