状态管理V2 鸿蒙开发更好用状态管理的装饰器?

前言:

ArkTs 采用的是MVVM 的数据架构,为了更加趋近MVVM 模式,状态管理装饰器由先前的V1推进到了V2

宏观局别

V1强调组件层级的状态管理,而V2则增强了对数据对象的深度观察与管理能力,不再局限于组件层级。通过V2,开发者能够更灵活地控制数据和状态,实现更高效的UI刷新。

V2新增装饰器

@ComponentV2

当我们需要使用V2装饰器时,需要提前声明这个组件是V2版本的组件,否则无法使用V2中所包含的所有装饰器。

js 复制代码
@ComponentV2 // 装饰器
struct Index { // struct声明的数据结构
  build() { // build定义的UI
  }
}

@Local

V1当中装饰组件内部的状态变量采用的是@State,在V2当中的@Local与其功能类似但并不完全相同。

对于简单的数据类型,两者并没有任何区别

但对于复杂数据类型,两者有本质的区别

js 复制代码
interface iPerson{
  name:string;
  age:number;
}

@Entry
@ComponentV2
struct Index {
  @Local num: number = 0;
  @Local person:iPerson={name:'张三',age:30}
  build() {
    Column(){
      Text('num:'+this.num)
      Text(this.person.name+':'+this.person.age)
      Divider()
      Button('++')
        .onClick(() => {
          this.num++;
          this.person.age++
        })
      Button('覆盖')
        .onClick(() => {
          this.person={name:'李四',age:40}
        })
    }
    .width('100%')
  }
}

如上图所示,@Local 并不能和@State一样监听到第一层的数据,但是对于直接覆盖整个对象的操作确实和@State一样能够被监听到。

@ObservedV2 & @Trace

@ObservedV2@Trace提供了对嵌套类对象属性变化直接观测的能力,是状态管理V2中相对核心的能力之一。

V1@Observed一样都只能对class声明的对象类型使用。

通常配合@Trace一起使用,@Trace装饰器装饰的属性property变化时,仅会通知property关联的组件进行刷新。

值得一提的是@ObservedV2并不支持使用JSON.stringify进行序列化。

js 复制代码
@ObservedV2
class iPerson {
  @Trace name: string;
  @Trace age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

@Entry
@ComponentV2
struct Index {
  @Local p1:iPerson = new iPerson('张三', 18)
  build() {
    Column() {
      Text('name:' + this.p1.name)
      Text(this.p1.name + ':' + this.p1.age)
      Text(JSON.stringify(this.p1))
      Button('++')
        .onClick(() => {
          this.p1.age++;
        })
      Divider()
    }
    .width('100%')

  }
}

如上图所示,使用JSON时,即使age变化了,依然无法监听到。

@Param

这个装饰器主要是用来接受来自父组件传递的数据的,他并不具备修改数据的能力。

如果是对于基本数据而言只能对其进行读操作,不能改变基本数据类型的值编译器会直接报错。

但对于复杂的数据类型,可以配合上文提到的@Trace使用,完成对复杂数据类型的修改

js 复制代码
@ObservedV2
class iPerson {
  @Trace name: string;
  @Trace age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

@Entry
@ComponentV2
struct Index {
  @Local num:number=0
  @Local p1:iPerson = new iPerson('张三', 18)
  build() {
    Column() {
      Text(this.p1.name + ':' + this.p1.age)
      Text('num:'+this.num)

      Divider()
      Son({
        num: this.num,
        p1:this.p1
      })
    }
    .width('100%')

  }
}

@ComponentV2
struct Son {
  @Param num: number = 0
  @Param p1: iPerson = new iPerson('', 0)
  build() {
    Column(){
      Button('++')
        .onClick(() => {
          this.p1.age++;
          // this.num++;
        })
    }
  }
}

@Once

@Once必须 搭配@Param使用,单独使用或搭配其他装饰器使用都是不允许的。

为了弥补在前文中提到的@Param无法修改父组件传递的值,当@Once搭配其使用时能使得@Param具备了这个能力,但修改的状态不会影响父组件。

js 复制代码
@ObservedV2
class iPerson {
  @Trace name: string;
  @Trace age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

@Entry
@ComponentV2
struct Index {
  @Local num:number=0
  @Local p1:iPerson = new iPerson('张三', 18)
  build() {
    Column() {
      Text(this.p1.name + ':' + this.p1.age)
      Text('num:'+this.num)

      Divider()
      Son({
        num: this.num,
        p1:this.p1
      })
    }
    .width('100%')

  }
}

@ComponentV2
struct Son {
  @Once @Param num: number = 0
  @Once @Param p1: iPerson = new iPerson('', 0)
  build() {
    Column(){
      Text('子组件Once修饰的num:'+this.num)
      Button('++')
        .onClick(() => {
          this.p1.age++;
          this.num++;
        })
    }
  }
}

由于是仅针对数据源的变化做拦截,对于复杂数据类型并不影响。

@Event

@Event主要配合@Param实现数据的双向同步

由于@Param装饰的变量在本地无法更改,使用@Event装饰器装饰回调方法并调用,可以实现更改数据源的变量,再通过@Local的同步机制,将修改同步回@Param,以此达到主动更新@Param装饰变量的效果。

js 复制代码
@ObservedV2
class iPerson {
  @Trace name: string;
  @Trace age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

@Entry
@ComponentV2
struct Index {
  @Local num:number=0
  @Local p1:iPerson = new iPerson('张三', 18)
  // @Local num2:number=0
  build() {
    Column() {
      Text(this.p1.name + ':' + this.p1.age)
      Text('num:'+this.num)
      Divider()
      Son({
        num: this.num,
        p1:this.p1,
        handler:(n:number)=>{
          this.num+=n
          this.p1.age+=n
        }
      })
    }
    .width('100%')

  }
}

@ComponentV2
struct Son {
  @Param num: number = 0
  @Param p1: iPerson = new iPerson('', 0)
  @Event handler:(num:number)=>void
  build() {
    Column(){
      Text(this.p1.name + ':' + this.p1.age)
      Text('num:'+this.num)
      Button('+1')
        .onClick(() => {
          this.handler(1);
        })
      Button('+10')
        .onClick(() => {
          this.handler(10);
        })
    }
  }
}

如上述代码所示,使用@event从父组件中接受到方法handler,将参数的逻辑写入到子组件中,通过父组件调用该方法,从而实现了父子组件的数据双向绑定。

@Monitor

@Monitor装饰器用于监听状态变量修改,使得状态变量具有深度监听的能力

@Monitor 能够收集所有被他监视的状态改变的属性,并将该属性记录下来。改变的属性均会被记录在dirty数组当中。

js 复制代码
@ObservedV2
class iPerson {
  @Trace name: string;
  @Trace age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

@Entry
@ComponentV2
struct Index {
  @Local num1:number=100
  @Local num2:number=200
  @Monitor("num1","num2")
  handler(monitor:IMonitor){
    console.log('-----monitor.value:',JSON.stringify(monitor.dirty))
    monitor.dirty.forEach((item)=>{
      console.log("-----",JSON.stringify(monitor.value(item)))
    })

  }
  build() {
    Column() {
      Text('num1:'+this.num1)
      Text('num2:'+this.num2)
      Button('+1')
        .onClick(() => {
          this.num1++;
          this.num2++;
        })
    }
    .width('100%')
  }
}

@Computed

@Computed为方法装饰器,装饰getter方法。@Computed会检测被计算的属性变化,当被计算的属性变化时,@Computed只会被求解一次

用法与vue3中的计算属性十分相似。同样也具有缓存能力

js 复制代码
@Entry
@ComponentV2
struct Index {
  @Local num1:number=100
  @Local num2:number=200
  @Computed get sum(){
    return this.num1+this.num2
  }

  build() {
    Column() {
      Text('num1:'+this.num1)
      Text('num2:'+this.num2)
      Text('sum:'+this.sum)
      Button('+1')
        .onClick(() => {
          this.num1++;
          this.num2++;
        })
    }
    .width('100%')
  }
}
相关推荐
一只大侠的侠13 小时前
Flutter开源鸿蒙跨平台训练营 Day 10特惠推荐数据的获取与渲染
flutter·开源·harmonyos
崔庆才丨静觅15 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606116 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了16 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅16 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅16 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅17 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment17 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅17 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端