鸿蒙进阶-状态管理之@Prop@Link
大家好啊,这里是鸿蒙开天组,今天我们来学习状态管理。
开始组件化开发之后,如何管理组件的状态会变得尤为重要,咱们接下来系统的学习一下这部分的内容
状态管理机制
在声明式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: stringage: numbercolor: string
}const c: Chicken = {name: '1',age: 2,color: '黄绿色'
}
// 获取对象的属性名,返回字符串数组
console.log('', Object.keys(c))// name,age,color//复杂数据类型且嵌套interface Dog {name: string
}interface Person {name: stringdog: 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 = 0build() {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 {// 编写 UIbuild() {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: stringage: 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// 编写 UIbuild() {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)}
}
好啦,今天的内容就到这里,感谢大家的观看,我们下次再见!