状态管理概述
在声明式UI编程框架中,UI是程序状态的运行结果,用户构建了一个UI模型,其中应用的运行时的状态是参数。当参数改变时,UI作为返回结果,也将进行对应的改变。这些运行时的状态变化所带来的UI的重新渲染,在ArkUI中统称为状态管理机制。
自定义组件拥有变量,变量必须被装饰器装饰才可以成为状态变量,状态变量的改变会引起UI的渲染刷新。如果不使用状态变量,UI只能在初始化时渲染,后续将不会再刷新。 下图展示了State和View(UI)之间的关系。
- View(UI):UI渲染,指将build方法内的UI描述和@Builder装饰的方法内的UI描述映射到界面。
- State:状态,指驱动UI更新的数据。用户通过触发组件的事件方法,改变状态数据。状态数据的改变,引起UI的重新渲染。
基本概念
- 状态变量:被状态装饰器装饰的变量,状态变量值的改变会引起UI的渲染更新。示例:@State num: number = 1,其中,@State是状态装饰器,num是状态变量。
- 常规变量:没有被状态装饰器装饰的变量,通常应用于辅助计算。它的改变永远不会引起UI的刷新。以下示例中increaseBy变量为常规变量。
- 数据源/同步源:状态变量的原始来源,可以同步给不同的状态数据。通常意义为父组件传给子组件的数据。以下示例中数据源为count: 1。
- 命名参数机制:父组件通过指定参数传递给子组件的状态变量,为父子传递同步参数的主要手段。示例:CompA: ({ aProp: this.aProp })。
- 从父组件初始化:父组件使用命名参数机制,将指定参数传递给子组件。子组件初始化的默认值在有父组件传值的情况下,会被覆盖。示例:
@Component
struct MyComponent {
//状态变量
@State count: number = 0;
//常规变量
increaseBy: number = 1;
build() {
}
}
@Component
struct Parent {
build() {
Column() {
// 从父组件初始化,覆盖本地定义的默认值
MyComponent({ count: 1, increaseBy: 2 })
}
}
}
- 初始化子组件:父组件中状态变量可以传递给子组件,初始化子组件对应的状态变量。示例同上。
- 本地初始化:在变量声明的时候赋值,作为变量的默认值。示例:@State count: number = 0。
装饰器总览
ArkUI提供了多种装饰器,通过使用这些装饰器,状态变量不仅可以观察在组件内的改变,还可以在不同组件层级间传递,比如父子组件、跨组件层级,也可以观察全局范围内的变化。根据状态变量的影响范围,将所有的装饰器可以大致分为:
- 管理组件拥有状态的装饰器:组件级别的状态管理,可以观察组件内变化,和不同组件层级的变化,但需要唯一观察同一个组件树上,即同一个页面内。
- 管理应用拥有状态的装饰器:应用级别的状态管理,可以观察不同页面,甚至不同UIAbility的状态变化,是应用内全局的状态管理。
:::success
咱们来看一张完整的装饰器说明图,咱们后续的学习就围绕着这张图来展开
:::
- 管理组件状态:小框中
- 管理应用状态:大框中
@State自己的状态
@State 装饰器咱们已经学习过了,所以就不从头讲解,而是说2 个使用的注意点
:::success
观察变化注意点:
并不是状态变量的所有更改都会引起UI的刷新,只有可以被框架观察到的修改才会引起UI刷新。
- 当装饰的数据类型为boolean、string、number类型时,可以观察到数值的变化。
- 当装饰的数据类型为class或者Object时,可以观察到自身的赋值的变化,和其属性赋值的变化,即Object.keys(observedObject)返回的所有属性。例子如下。声明ClassA和Model类。
:::
**<font style="color:rgb(27, 27, 27);">Object.keys()</font>**
静态方法返回一个由给定对象自身的可枚举的字符串键属性名组成的数组。
基本数据类型
// for simple type
@State count: number = 0;
// value changing can be observed
this.count = 1;
interface Chicken {
name: string
age: number
color: string
}
const c: Chicken = {
name: '1',
age: 2,
color: '黄绿色'
}
// 获取对象的属性名,返回字符串数组
console.log('', Object.keys(c))
复杂数据类型且嵌套
interface Dog {
name: string
}
interface Person {
name: string
dog: Dog
}
@Component
export struct HelloComponent {
// 状态变量
@State message: string = 'Hello, World!';
@State person: Person = {
name: 'jack',
dog: {
name: '柯基'
}
}
sayHi() {
console.log('你好呀')
}
build() {
Column() {
Text(this.message)
Button('修改 message')
.onClick(() => {
this.message = 'Hello,ArkTS'
})
Text(JSON.stringify(this.person))
Button('修改title外层属性')
.onClick(() => {
this.person.name = '666'
})
Button('修改title嵌套属性')
.onClick(() => {
this.person.dog.name = '内部的 666'
// this.person.dog = {
// name: '阿拉斯加'
// }
})
}
}
}
@Prop父传子_单向传递数据
@Prop 装饰的变量可以和父组件建立单向的同步关系。@Prop 装饰的变量是可变的,但是变化不会同步回其父组件。
@Component
struct SonCom {
build() {
}
}
@Entry
@Component
// FatherCom 父组件
struct FatherCom {
build() {
Column() {
// 子组件
SonCom()
}
}
}
简单类型
简单类型 string、number、boolean、enum
:::success
注意:
- 修改父组件数据,会同步更新子组件
- 修改子组件@Prop 修饰的数据,子组件 UI 更新,但会被父组件状态更新被覆盖
- 通过回调函数的方式修改父组件的数据,然后触发@Prop数据的更新
:::
基础模版:
@Component
struct SonCom {
@Prop info: string
build() {
Button('info:' + this.info)
.onClick(() => {
this.info = '子组件修改'
})
}
}
@Entry
@Component
struct FatherCom {
@State info: string = '么么哒'
build() {
Column() {
Text(this.info)
.onClick(() => {
this.info = '父组件修改'
})
SonCom({
info: this.info,
})
}
.padding(20)
.backgroundColor(Color.Orange)
}
}
:::success
步骤:
- 子组件定义修改数据的函数(箭头函数)
- 父组件,传入回调函数(必须是箭头函数),
- 回调函数中修改数据,并修改
- 如果要传递参数,定义形参,并传入实参即可
:::
@Component
struct SonCom {
@Prop info: string
changeInfo = (newInfo: string) => {
}
build() {
Button('info:' + this.info)
.onClick(() => {
this.changeInfo('改啦')
})
}
}
@Entry
@Component
struct FatherCom {
@State info: string = '么么哒'
build() {
Column() {
Text(this.info)
SonCom({
info: this.info,
changeInfo: (newInfo: string) => {
this.info = newInfo
}
})
}
.padding(20)
.backgroundColor(Color.Orange)
}
}
复杂类型
复杂类型的做法类似,通过回调函数将需要修改的数据传递给父组件即可
基础模版:
interface User {
name: string
age: number
}
@Entry
@Component
struct Index {
@State
userInfo: User = {
name: 'jack',
age: 18
}
build() {
Column({ space: 20 }) {
Text('父组件')
.fontSize(30)
Text('用户名:' + this.userInfo.name)
.white()
.onClick(() => {
this.userInfo.name = 'rose'
})
Text('年龄:' + this.userInfo.age)
.white()
.onClick(() &#