大家好啊,这里是鸿蒙开天组,今天我们来学习状态管理。
开始组件化开发之后,如何管理组件的状态会变得尤为重要,咱们接下来系统的学习一下这部分的内容
状态管理机制
在声明式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 })。
- 从父组件初始化:父组件使用命名参数机制,将指定参数传递给子组件。子组件初始化的默认值在有父组件传值的情况下,会被覆盖。示例:
@Entry
@Component
struct Parent {
build() {
Column() {
// 从父组件初始化,覆盖本地定义的默认值
ChildComponent({ count: 1,})
}
}
}
@Component
struct ChildComponent {
// 状态变量,更改会触发 UI 刷新
@State count: number = 0;
build() {
Text(this.count.toString())
}
}
- 初始化子组件:父组件中状态变量可以传递给子组件,初始化子组件对应的状态变量。示例同上。
- 本地初始化:在变量声明的时候赋值,作为变量的默认值。示例:@State count: number = 0。
装饰器总览
ArkUI提供了多种【装饰器】,通过使用这些装饰器,状态变量不仅可以观察在组件内的改变,还可以在不同组件层级间传递,比如父子组件、跨组件层级,也可以观察全局范围内的变化。根据状态变量的影响范围,将所有的装饰器可以大致分为:
- 管理组件拥有状态的装饰器:组件级别的状态管理,可以观察组件内变化,和不同组件层级的变化,但需要唯一观察同一个组件树上,即同一个页面内。
- 管理应用拥有状态的装饰器:应用级别的状态管理,可以观察不同页面,甚至不同UIAbility的状态变化,是应用内全局的状态管理。
下面这张图就是完整的装饰器说明图,咱们后续的学习就围绕着这张图来展开
- 管理组件状态:小框中(目前专注这个即可)
- 管理应用状态:大框中
@State 自己的状态
@State 装饰器咱们已经学习过了,所以就不从头讲解,而是说2 个使用的注意点
观察变化注意点:
并不是状态变量的所有更改都会引起UI的刷新,只有可以【被框架观察到】的修改才会引起UI刷新。
- 当装饰的数据类型为boolean、string、number类型时,可以观察到数值的变化。
- 当装饰的数据类型为class或者Object时,可以观察到自身的赋值的变化,和其属性赋值的变化,即Object.keys(observedObject)返回的所有属性。例子如下。声明ClassA和Model类。
// 基本数据类型
@State count: number = 0;
// 可以观察到数值变化
this.count = 1;
//Object.keys 测试
interface Chicken {
name: string
age: number
color: string
}
const c: Chicken = {
name: '1',
age: 2,
color: '黄绿色'
}
// 获取对象的属性名,返回字符串数组
console.log('', Object.keys(c))// name,age,color
//复杂数据类型且嵌套
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(() => {
// 修改嵌套属性,无法被监听,UI 不更新
this.person.dog.name = '内部的 666'
// 修改第一层属性,可以被监听,UI 更新
// this.person.dog = {
// name: '阿拉斯加'
// }
})
}
}
}
@Prop 父子单向
@Prop 装饰的变量可以和父组件建立单向的同步关系。@Prop 装饰的变量是可变的,但是变化不会同步回其父组件。(不要直接修改 Prop 的值)
@Component
struct SonCom {
// 默认值可以省略,不设置为 undefined
@Prop xxx:类型='可选默认值'
build() {
}
}
@Entry
@Component
// FatherCom 父组件
struct FatherCom {
build() {
Column() {
// 子组件
SonCom({xxx:'具体的值'})
}
}
}
@Link双向同步
使用步骤:
- 将父组件的状态属性传递给子组件
- 子组件通过@Link修饰即可
- 分别测试基础,和复杂类型
基础模板
@Entry
@Component
// 父组件
struct KnowledgePage {
@State count: number = 0
build() {
Column() {
Text('父组件')
.fontSize(30)
Text(this.count.toString())
Button('修改数据')
.onClick(() => {
})
SonComponent()
}
.padding(10)
.height('100%')
.backgroundColor('#ccc')
.width('100%')
.alignItems(HorizontalAlign.Center)
.padding({ top: 100 })
}
}
@Component
// 子组件
struct SonComponent {
// 编写 UI
build() {
Column({ space: 20 }) {
Text('我是子组件')
.fontSize(20)
Column() {
Button('修改count')
.onClick(() => {
})
}
}
.backgroundColor('#a6c398')
.alignItems(HorizontalAlign.Center)
.width('80%')
.margin({ top: 100 })
.padding(10)
.borderRadius(10)
}
}
参考代码
interface Person {
name: string
age: number
}
@Entry
@Component
// 父组件
struct Page03_Link {
@State count: number = 0
@State p: Person = {
name: 'jack',
age: 18,
}
build() {
Column() {
Text('父组件')
.fontSize(30)
Text(this.count.toString())
Text(JSON.stringify(this.p))
Button('修改数据')
.onClick(() => {
this.count--
})
SonComponent({
count: this.count,
p: this.p
})
}
.padding(10)
.height('100%')
.backgroundColor('#ccc')
.width('100%')
.alignItems(HorizontalAlign.Center)
.padding({ top: 100 })
}
}
@Component
// 子组件
struct SonComponent {
// 通过 @Link 来接收
@Link count: number
@Link p: Person
// 编写 UI
build() {
Column({ space: 20 }) {
Text('我是子组件')
.fontSize(20)
Text(this.count.toString())
Text(JSON.stringify(this.p))
Column() {
Button('修改count')
.onClick(() => {
this.count++
})
Button('修改Person')
.onClick(() => {
this.p.age++
})
}
}
.backgroundColor('#a6c398')
.alignItems(HorizontalAlign.Center)
.width('80%')
.margin({ top: 100 })
.padding(10)
.borderRadius(10)
}
}
好啦,今天的内容就到这里,感谢大家的观看,我们下次再见!