1、说在前面的话

在前五个章节中,我们构建的页面基本都是静态的页面,如果希望构建一个动态的、有交互的界面,就需要引入“状态”的概念,以便随着用户的交互,界面随着发生变化,例如如下的动图:

上面的示例中,用户与应用程序的交互触发了文本状态变更,状态变更引起了UI渲染,UI从“Hello World”变更为“Hello ArkUI”。

在声明式UI编程框架中,UI是程序状态的运行结果,用户构建了一个UI模型,其中应用的运行时的状态是参数。当参数改变时,UI作为返回结果,也将进行对应的改变。这些运行时的状态变化所带来的UI的重新渲染,在ArkUI中统称为状态管理机制。

自定义组件拥有变量,变量必须被装饰器装饰才可以成为状态变量,状态变量的改变会引起UI的渲染刷新。如果不使用状态变量,UI只能在初始化时渲染,后续将不会再刷新。下图展示了State和View(UI)之间的关系。

  • View(UI):UI渲染,指将build方法内的UI描述和@Builder装饰的方法内的UI描述映射到界面。

  • State:状态,指驱动UI更新的数据。用户通过触发组件的事件方法,改变状态数据。状态数据的改变,引起UI的重新渲染。

1.1、一些基本概念

  • 状态变量

被状态装饰器(@State)装饰的变量,状态变量值的改变会引起UI的渲染更新。示例如下,其中@State是状态装饰器,num是状态变量。

@Component
struct MyComponent {
@State num: number = 1;
build() {
// ...
}
}
  • 常规变量

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

  • 数据源/同步源

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

@Component
struct MyComponent {
@State count: number = 0;
build() {
}
}
@Component
struct Parent {
build() {
Column() {
// 从父组件初始化,覆盖本地定义的默认值
MyComponent({ 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

1.2、组件的状态概览

组件的状态装饰器主要有以下几种:

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

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

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

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

  • @Observed:@Observed装饰class,需要观察多层嵌套场景的class需要被@Observed装饰。单独使用@Observed没有任何作用,需要和@ObjectLink、@Prop连用。

  • @ObjectLink:@ObjectLink装饰的变量接收@Observed装饰的class的实例,应用于观察多层嵌套场景,和父组件的数据源构建双向同步。

注意:

仅@Observed/@ObjectLink可以观察嵌套场景,其他的状态变量仅能观察第一层

下面,将逐一介绍这六种组件的状态管理装饰器。

2、@State装饰器:组件内状态

@State装饰的变量(状态变量),一旦变量拥有了状态属性,就和自定义组件的渲染绑定起来。当状态改变时,UI会发生对应的渲染改变。在状态变量相关装饰器中,@State是最基础的,使变量拥有状态属性的装饰器,它也是大部分状态变量的数据源。

@State装饰的变量,与声明式范式中的其他被装饰变量一样,是私有的,只能从组件内部访问,在声明时必须指定其类型和本地初始化。初始化也可选择使用命名参数机制从父组件完成初始化。

@State装饰的变量拥有以下特点:

  • @State装饰的变量不与父组件中任何类型的变量同步。

  • @State装饰的变量与子组件中的@Prop装饰变量之间建立单向数据同步,与@Link、@ObjectLink装饰变量之间建立双向数据同步。

  • @State装饰的变量生命周期与其所属自定义组件的生命周期相同。

  • @State装饰的变量类型可以为:Object、class、string、number、boolean、enum类型,以及这些类型的数组,不支持any,不支持简单类型和复杂类型的联合类型,不允许使用undefined和null。【建议不要装饰Date类型,应用可能会产生异常行为。不支持Length、ResourceStr、ResourceColor类型,Length、ResourceStr、ResourceColor为简单类型和复杂类型的联合类型。】

  • 可以从本地初始化,也可以从父组件初始化。如果从父组件初始化,将覆盖本地初始化。支持父组件中常规变量、@State、@Link、@Prop、@Provide、@Consume、@ObjectLink、@StorageLink、@StorageProp、@LocalStorageLink和@LocalStorageProp装饰的变量,初始化子组件的@State。

  • @State装饰的变量支持初始化子组件的常规变量、@State、@Link、@Prop、@Provide。

  • 不支持在组件外访问@State变量。

2.1、观察变化和行为表现

并不是状态变量的所有更改都会引起UI的刷新,只有可以被框架观察到的修改才会引起UI刷新。本小节将介绍什么样的修改才能被观察到,以及观察到变化后,框架的是怎么引起UI刷新的,即框架的行为表现是什么。

2.1、观察变化

  • 当装饰的数据类型为boolean、string、number类型时,可以观察到数值的变化。
// for simple type
@State count: number = 0;
// value changing can be observed
this.count = 1;
  • 当装饰的数据类型为class或者Object时,可以观察到自身的赋值的变化,和其属性赋值的变化,即Object.keys(observedObject)返回的所有属性,无法观察到属性中的嵌套属性的变化。例子如下。
// 1、 声明ClassA和Model类。
class ClassA {
public value: string;
constructor(value: string) {
this.value = value;
}
}
class Model {
public value: string;
public name: ClassA;
constructor(value: string, a: ClassA) {
this.value = value;
this.name = a;
}
}
// 2、 @State装饰的类型是Model
// class类型
@State title: Model = new Model('Hello', new ClassA('World'));
// 对@State装饰变量的赋值。
// class类型赋值(可以观测到变化)
this.title = new Model('Hi', new ClassA('ArkUI'));
// 对@State装饰变量的属性赋值。
// class属性的赋值(可以观测到变化)
this.title.value = 'Hi';
// 嵌套的属性赋值观察不到
this.title.name.value = 'ArkUI';
  • 当装饰的对象是array时,可以观察到数组本身的赋值和添加、删除、更新数组的变化。例子如下。
// 1、声明Model类。
class Model {
public value: number;
constructor(value: number) {
this.value = value;
}
}
// @State装饰的对象为Model类型数组时。
@State title: Model[] = [new Model(11), new Model(1)];
// 数组自身的赋值可以观察到。
this.title = [new Model(2)];
// 数组项的赋值可以观察到。
this.title[0] = new Model(2);
// 删除数组项可以观察到。
this.title.pop();
// 新增数组项可以观察到。
this.title.push(new Model(12));
// 数组项中属性的赋值观察不到。
this.title[0].value = 6;

2.1.2、框架行为

  • 当状态变量被改变时,查询依赖该状态变量的组件;

  • 执行依赖该状态变量的组件的更新方法,组件更新渲染;

  • 和该状态变量不相关的组件或者UI描述不会发生重新渲染,从而实现页面渲染的按需更新。

3、@State装饰器Demo

Demo效果如下:

  • 自定义组件MyComponent定义了被@State装饰的状态变量count和title,其中title的类型为自定义类Model。如果count或title的值发生变化,则查询MyComponent中使用该状态变量的UI组件,并进行重新渲染。

  • EntryComponent中有多个MyComponent组件实例,第一个MyComponent内部状态的更改不会影响第二个MyComponent。

class Model {
public value: string; constructor(value: string) {
this.value = value;
}
} @Entry
@Component
struct EntryComponent {
build() {
Column() {
// 此处指定的参数都将在初始渲染时覆盖本地定义的默认值,并不是所有的参数都需要从父组件初始化
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变量的更新将触发上面的Text组件内容更新
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变量的更新将触发该Button组件的内容更新
this.count += this.increaseBy;
})
.width(300)
.margin(10)
}
}
}

从该示例中,我们可以了解到@State变量首次渲染的初始化流程:

  1. 使用默认的本地初始化:
    @State title: Model = new Model('Hello World');
@State count: number = 0;

2. 对于@State来说,命名参数机制传递的值并不是必选的,如果没有命名参数传值,则使用本地初始化的默认值:

    MyComponent({ count: 1, increaseBy: 2 })

4、结语

状态管理的内容量比较大,为了阅读的舒适性,本文先介绍@State装饰器。

后续:@Prop、@Link、@Provide/@Consume、@Observed、@ObjectLink将分别介绍,持续关注 "鸿蒙UI开发快速入门 —— part07"。

鸿蒙UI开发快速入门 —— part06:组件状态管理之@State装饰器的更多相关文章

  1. Transform组件C#游戏开发快速入门

    Transform组件C#游戏开发快速入门大学霸 组件(Component)可以看作是一类属性的总称.而属性是指游戏对象上一切可设置.调节的选项,如图2-8所示.本文选自C#游戏开发快速入门大学霸   ...

  2. HealthKit开发快速入门教程之HealthKit框架体系创建健康AppID

    HealthKit开发快速入门教程之HealthKit框架体系创建健康AppID HealthKit开发准备工作 在开发一款HealthKit应用程序时,首先需要讲解HealthKit中有哪些类,在i ...

  3. HealthKit开发快速入门教程之HealthKit开发概述简介

    HealthKit开发快速入门教程之HealthKit开发概述简介 2014年6月2日召开的年度开发者大会上,苹果发布了一款新的移动应用平台,可以收集和分析用户的健康数据.该移动应用平台被命名为“He ...

  4. Apple Watch开发快速入门教程

     Apple Watch开发快速入门教程  试读下载地址:http://pan.baidu.com/s/1eQ8JdR0 介绍:苹果为Watch提供全新的开发框架WatchKit.本教程是国内第一本A ...

  5. SpringBoot开发快速入门

    SpringBoot开发快速入门 目录 一.Spring Boot 入门 1.Spring Boot 简介 2.微服务 3.环境准备 1.maven设置: 2.IDEA设置 4.Spring Boot ...

  6. HealthKit开发快速入门教程之HealthKit数据的操作

    HealthKit开发快速入门教程之HealthKit数据的操作 数据的表示 在HealthKit中,数据是最核心的元素.通过分析数据,人们可以看到相关的健康信息.例如,通过统计步数数据,人们可以知道 ...

  7. 游戏控制杆OUYA游戏开发快速入门教程

    游戏控制杆OUYA游戏开发快速入门教程 1.2.2  游戏控制杆 游戏控制杆各个角度的视图,如图1-4所示,它的硬件规格是本文选自OUYA游戏开发快速入门教程大学霸: 图1-4  游戏控制杆各个角度的 ...

  8. WPF开发快速入门【7】WPF的拖放功能(Drag and Drop)

    概述 本文描述WPF的拖放功能(Drag and Drop). 拖放功能涉及到两个功能,一个就是拖,一个是放.拖放可以发生在两个控件之间,也可以在一个控件自己内部拖放.假设界面上有两个控件,一个Tre ...

  9. Web前端的状态管理(State Management)

    背景 我相信很多朋友跟我一样,初次听到什么Flux, Redux, Vuex,状态管理的时候是一脸懵逼的.因为在外面之前前端大部分开发的时候,根本没有那么多的概念.自从ReactJS火爆后,什么Flu ...

  10. ReactJS入门2:组件状态

    React组件可以简单看做是包含props和states的函数. 上一节总结了创建新组建和数据属性的传递.本节主要讲解组件的状态. React认为UI是不同状态的展现.在React中,开发者只需更新组 ...

随机推荐

  1. 面试官的几句话,差点让我挂在HTTPS上

    作为软件测试,大家都知道一些常用的网络协议是我们必须要了解和掌握的,比如 HTTP 协议,HTTPS 协议就是两个使用非常广泛的协议,所以也是面试官问的面试的时候问的比较多的两个协议:而且因为这两个协 ...

  2. 技术储备--SPI接口硬件协议栈芯片W5500使用

    整体思路: 移植官方软件包代码, 配置好底层的SPI收发函数以及片选脚操作, 至于临界区操作函数,根据实际情况进行添加,也可以不加. 这就移植好了,就可以在我们自己的板子上跑官方的示例程序了. 官方软 ...

  3. Vue3——Vue Router

    安装 vue-router 依赖包 npm install vue-router@4 创建 router 文件夹,然后在里面创建一个 index.ts 文件,用于定义你的路由配置 // index.t ...

  4. Vue 文件流预览 PDF

    Vue js // pdf 预览 export function download(id) { return request({ url: '/bbs/regtech/law/download?id= ...

  5. Flutter 实现骨架屏

    什么是骨架屏 在客户端开发中,我们总是需要等待拿到服务端的响应后,再将内容呈现到页面上,那么在用户发起请求到客户端成功拿到响应的这段时间内,应该在屏幕上呈现点什么好呢? 答案是:骨架屏 那么什么是骨架 ...

  6. 内核模块踩内存问题定位利器- hardware breakpoint

    内核由于共享内存地址空间,如果没有合适的工具,很多踩内存的问题即使复现,也无法快速定位: 在新的内核版本中引入了一个新工具hardware breakpoint,其能够监视对指定的地址的特定类型(读/ ...

  7. Trace32 simulator调试以及简单实用命令介绍

    目录 Trace32 Simulator debug Trace32工具配置 Trace32命令简介 memory class 常见命令索引 v.v使用实例 不同CPU运行信息查看 Trace32 S ...

  8. 进程管理中的active_mm是做什么的?

    在Linux内核中,进程管理涉及到许多复杂的数据结构和机制,其中active_mm是与内存管理相关的一个关键概念.理解active_mm需要先了解与之相关的一些基本内核结构和概念. 基本概念 mm_s ...

  9. 2. 王道OS-操作系统的特征,发展和分类

    1. 并发:宏观上是同时发生的,微观是交替发生的 :ps:并行:宏观和微观都是同时发生的 : ps:单核CPU同一时刻只能执行一个程序,各个程序只能并发的执行 : 多核CPU同一时刻可以同时执行多个程 ...

  10. apisix~按域名进行请求转发

    路由route 路由(Route)是请求的入口点,它定义了客户端请求与服务之间的匹配规则.路由可以与服务(Service).上游(Upstream)关联,一个服务可对应一组路由,一个路由可以对应一个上 ...