@State,@Prop,@Link
关系如下图,希望大家能看的懂
本人也是作为一个初学者,用一个例子来讲下ArkUI中的状态管理是如实实现的。
这里涉及到三个ets文件
主界面:
PropPage.ets
任务统计组件:
TaskStatistics.ets
任务列表组件:
TaskList.ets
任务模型文件:
Task.ts
(代码比较少,先贴上)
/**
* 任务
*/
export class Task{
//任务自增ID
static id: number = 1
//任务名称
name: string = '任务'+ (Task.id++)
//是否完成
finish: boolean = false
}
例子效果图
一、@State装饰器使用
在截屏页面也就是PropPage
(变量值发生变化及时触发UI渲染)
代码如下
import { TaskList } from '../components/TaskList'
import { TaskStatistics } from '../components/TaskStatistics'
import { Task } from '../model/Task'
@Entry
@Component
struct PropPage {
//所有任务
@State totalTask: number = 0
//已完成的任务
@State finishedTask: number = 0
//所有任务
@State tasks: Array<Task> = [
new Task(),
new Task(),
]
onPageShow(){
this.totalTask = this.tasks.length
}
build() {
Column({ space: 8 }) {
//上半部分-任务进度卡片
TaskStatistics({totalTask: this.totalTask, finishedTask: this.finishedTask})
//下半部分-任务列表
TaskList({tasks: $tasks, totalTask: $totalTask, finishedTask: $finishedTask})
}
.width('100%')
.height('100%')
.backgroundColor('#F1F2F3')
}
}
就这么简单,只需要在变量前面添加@State装饰器即可
特别注意的是
- 被@State装饰器标记的变量必须有初始值;
- @State支持所有常用变量,比如string、number、enum、boolean、object;
- 嵌套对象的值发生变化不会触发UI更新,比如
objectA.objectB.name
值发生变化就不会触发UI更新
二、@Prop装饰器使用
父组件通知子组件数据刷新,子组件定义的变量属性可以用上@Prop
装饰器。
特别注意:父组件可以通知子组件数据变更,而子组件数据变更并不能通知父组件,@Prop
是单向传递。
比如这个任务统计组件:TaskStatistics.ets
,它只需要显示数据,没有业务逻辑,只需要刷新数据即可。
//公共样式
@Styles function card() {
.width('95%')
.padding(20)
.backgroundColor(Color.White)
.borderRadius(15)
.shadow({ radius: 6, color: '#1F000000', offsetX: 2, offsetY: 4 })
}
@Component
export struct TaskStatistics{
//完成的任务数
@Prop finishedTask: number
//所有任务数
@Prop totalTask: number
build(){
//任务进度卡片
Row() {
Text('任务进度')
.fontWeight(FontWeight.Bold)
.fontSize(20)
Stack() {
Progress({ value: this.finishedTask, total: this.totalTask, type: ProgressType.Ring })
.width(80)
Text(this.finishedTask + '/' + this.totalTask)
.fontSize(15)
}
}
.justifyContent(FlexAlign.SpaceEvenly)
.margin({ top: 10 })
.card()
}
}
再特别注意的是
- 被@Prop装饰器标记的变量不能有初始值,只能由外部传递;
- @Prop只支持string、number、enum、boolean四个数据类型,不能定义object,array,any数据类型;
三、@Link装饰器使用
父组件与子组件数据可以相互刷新,子组件定义的变量属性只能用上@Link
装饰器。
比如这个任务列表组件:TaskList.ets
,它除了显示数据,还有业务逻辑,点击复选框勾选已完成的任务,然后同步到任务统计组件当中去,也可以点击新增任务按钮,增加任务到tasks数组中,同时同步到任务统计组件中去。
简而言之就是:子组件有业务逻辑并且会修改变量的值,这时就必须要用@Link装饰器去修饰变量(这里就好比是Java开发中的在组件内部直接操作类对象,组件外部的同一个类对象的值也跟着改变一样的道理。)
import { Task } from '../model/Task'
@Styles function card() {
.width('95%')
.padding(20)
.backgroundColor(Color.White)
.borderRadius(15)
.shadow({ radius: 6, color: '#1F000000', offsetX: 2, offsetY: 4 })
}
@Component
export struct TaskList {
//所有任务
@Link totalTask: number
//已完成的任务
@Link finishedTask: number
//所有任务
@Link tasks: Task[]
handTaskChange(){
this.totalTask = this.tasks.length
this.finishedTask = this.tasks.filter(item => item.finish).length
}
build() {
Column(){
Button('新增任务')
.margin({ top: 20, bottom: 20 })
.onClick((event) => {
this.tasks.push(new Task())
this.handTaskChange()
})
List({ space: 10 }) {
ForEach(this.tasks, (item: Task) => {
ListItem() {
Row() {
Text(item.name)
.fontSize(20)
Checkbox()
.select(item.finish)
.onChange((newValue) => {
item.finish = newValue
this.handTaskChange()
})
}
.justifyContent(FlexAlign.SpaceBetween)
.card()
}
})
}
}
}
}
特别注意的是
- 被@Link装饰器标记的变量一样不能有初始值,只能由外部传递;
- @Link支持string、number、enum、boolean,object,array数据类型,必须与父组件数据类型保持一致;
四、@Provide装饰器 与 @Consume装饰器使用
这两个装饰器装饰的变量,在组件之间数据传递也是双向的,跟@State与@Link相似,不同的是@Provide 与 @Consume父子组件之间无需参数传递,就好比是一个单例对象的内存共享一样,具体代码如下:
@Entry
@Component
export struct PropPage{
//所有任务
@Provide totalTask: number = 0
//已完成的任务
@Provide finishedTask: number = 0
//所有任务
@Provide tasks: Array<Task> = [
new Task(),
new Task(),
]
build(){
//上半部分-任务进度卡片
TaskStatistics()
//下半部分-任务列表
TaskList()
}
}
//TaskStatistics.ets关键代码如下
@Component
export struct TaskStatistics{
//完成的任务数
@Consume finishedTask: number
//所有任务数
@Consume totalTask: number
// ...其他代码与上面一致
}
是不是很简单,数据都是双向同步,唯一区别就是不需要传参,子组件可以直接使用或变更参数值。
@Provide 与 @Consume不推荐大家使用,除非真的有这种需求,一个变量到处都可以随意更改,代码可维护性太差,懂得都懂,尽可能的多使用@State,@Prop,@Link。