状态管理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%')
  }
}
相关推荐
Lingxing几秒前
深入浅出:从JS的new运算符到手写ES5/ES6版实现
前端·javascript·ecmascript 6
uhakadotcom3 分钟前
一步一步轻松安装和使用PySpark
后端·面试·github
用户9185824479734 分钟前
关于vue中的scoped
前端
木木黄木木8 分钟前
炫酷的3D按钮效果实现 - CSS3高级特性应用
前端·3d·css3
Kratos9 分钟前
鸿蒙状态管理中V1和V2的区别(3)(HarmonyOS API14版本)
harmonyos
木木黄木木12 分钟前
html5基于Canvas的经典打砖块游戏开发实践
前端·html·html5
顾言71612 分钟前
uniapp 和 webview 之间的通信
前端
bin915322 分钟前
DeepSeek 助力 Vue3 开发:打造丝滑的表格(Table)之添加导出数据功能示例7,TableView15_07带边框和斑马纹的导出表格示例
前端·javascript·vue.js·ecmascript·deepseek
木木黄木木1 小时前
html5炫酷3D立体文字效果实现详解
前端·3d·html5
我认不到你1 小时前
油候插件、idea、VsCode插件推荐(自用)
java·前端·vscode·react.js·typescript·编辑器·intellij-idea