鸿蒙开发-状态管理(V2试用版)

713 阅读2分钟

V2所属装饰器

1. @ObservedV2装饰器和@Trace装饰器:类属性变化观测

1.1 基本语法

@ObservedV2类装饰器:装饰class。需要放在class的定义前,使用new创建类对象。

@Trace成员变量装饰器:class中成员属性。属性的类型可以为number、string、boolean、class、Array、Date、Map、Set等类型。

@ObservedV2
class 类名 {
  @Trace 属性1:类型
  @Trace 属性2:类型
  属性3:类型
}

注意事项

  • 非@Trace装饰的成员属性用在UI上无法触发UI刷新
  • @Trace不能用在没有被@ObservedV2装饰的class上
  • @Trace是class中属性的装饰器,不能用在struct中
  • @ObservedV2、@Trace不能与@Observed@Track混合使用。
  • 使用@ObservedV2与@Trace装饰的类不能和@State等V1的装饰器混合使
  • 继承自@ObservedV2的类无法和@State等V1的装饰器混用
  • @ObservedV2的类实例目前不支持使用JSON.stringify进行序列化

1.2 基本使用

image.png

如下所示

recording.gif

1.3 详细使用

请参考@ObservedV2装饰器和@Trace装饰器---使用场景

参考代码

@ObservedV2
class Person {
  id: number
  @Trace name: string
  @Trace age: number
  constructor(id: number, name: string, age: number) {
    this.id = id
    this.name = name
    this.age = age
  }
}
@Entry
@ComponentV2
struct Index {
  person: Person = new Person(1, '张三', 18)
  build() {
    Column({ space: 20 }) {
      Text('@ObservedV2-@Trace')
        .fontSize(25)
      Column(){
        Row(){
          Text('id'+this.person.id)
          Button('修改数据')
          .onClick(()=>{
            this.person.id++
          })
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceBetween)
        Row(){
          Text('name'+this.person.name)
          Button('修改数据')
          .onClick(()=>{
            this.person.name = '李四'
          })
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceBetween)
        Row(){
          Text('age'+this.person.age)
          Button('修改数据')
          .onClick(()=>{
            this.person.age++
          })
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceBetween)
      }
      .width('100%')
      .backgroundColor(Color.Pink)
      }
    .backgroundColor('#cbe69b')
    .width('100%')
    .height('100%')
    .padding(20)
  }
}

2. @ComponentV2装饰器:自定义组件

1.1 基本语法

@ComponentV2@Component装饰器一样,@ComponentV2装饰器用于装饰自定义组件:

@ComponentV2 
struct Index { 
  build() { 
  }
}

注意事项

  • 在@ComponentV2装饰的自定义组件中,开发者仅可以使用全新的状态变量装饰器,包括@Local、@Param、@Once、@Event、@Provider、@Consumer等。
  • @ComponentV2装饰的自定义组件暂不支持组件复用、LocalStorage等现有自定义组件的能力。
  • 无法同时使用@ComponentV2与@Component装饰同一个struct结构。
  • @ComponentV2支持一个可选的boolean类型参数freezeWhenInactive,来实现组件冻结功能

3. @Local装饰器:组件内部状态

3.1 基本使用

除了无法从外部修改值,其他用法于与@State无异

@Entry
@ComponentV2
struct Index {
  @Local count: number = 0;
  @Local message: string = "Hello";
  @Local flag: boolean = false;
  build() {
    Column() {
      Text(`${this.count}`)
      Text(`${this.message}`)
      Text(`${this.flag}`)
      Button("change Local")
        .onClick(()=>{
          this.count++;
          this.message += " World";
          this.flag = !this.flag;
      })
    }
  }
}

recording.gif

注意事项

  • @Local装饰器只能在@ComponentV2装饰的自定义组件中使用。
  • @Local装饰的变量表示组件内部状态,不允许从外部传入初始化

image.png

  • 当装饰的变量类型是内置类型时,可以观察到变量整体赋值以及通过API调用带来的变化。
类型可观测变化的API
Arraypush、pop、shift、unshift、splice、copyWithin、fill、reverse、sort
DatesetFullYear, setMonth, setDate, setHours, setMinutes, setSeconds, setMilliseconds, setTime, setUTCFullYear, setUTCMonth, setUTCDate, setUTCHours, setUTCMinutes, setUTCSeconds, setUTCMilliseconds
Mapset, clear, delete
Setadd, clear, delete

3.2 @Local与@State对比

@State@Local
参数无。无。
从父组件初始化可选。不允许外部初始化。
观察能力能观测变量本身以及一层的成员属性,无法深度观测。能观测变量本身,深度观测依赖@Trace装饰器。
数据传递可以作为数据源和子组件中状态变量同步。可以作为数据源和子组件中状态变量同步。

3.3 详细使用

请参考@Local装饰器

4. @Param:组件外部输入

4.1 基本使用

@Param表示组件从外部传入的状态,使得父子组件之间的数据能够进行同步(单向传递)类似于@prop

@Entry
@ComponentV2
struct Index {
  @Local msg: string = 'Hello World'

  build() {
    Column() {
      Text('index==' + this.msg)
      son({
        msg: this.msg})
    }
    .width('100%')
    .height('100%')
  }
}

@ComponentV2
export struct son {
  @Param msg: string = ''
  build() {
    Column() {
      Text('son==' + this.msg)
        .onClick(() => {
          this.updata()
        })
    }
  }
}

image.png

修改值需要配合@Event

4.2 详细使用

请参考@Param:组件外部输入-V2所属装饰器

5. @Once:初始化同步一次

5.1 基本用法

@Once装饰器仅在变量初始化时接受外部传入值进行初始化,当后续数据源更改时,不会将修改同步给子组件

@Entry
@ComponentV2
struct Index {
  @Local msg: string = 'Hello World'

  build() {
    Column() {
      Text('index==' + this.msg)
      son({
        msg: this.msg, updata: () => {
          this.msg = 'hello v2'
        }
      })
    }
    .width('100%')
    .height('100%')
  }
}

@ComponentV2
export struct son {
  @Once @Param msg: string = ''
  @Event updata: () => void = () => {
  }

  build() {
    Column() {
      Text('son==' + this.msg)
        .onClick(() => {
          this.updata()
        })
    }
  }
}

recording.gif

6. @Event装饰器:组件输出

6.1 基本使用

类似于子传父时使用回调函数,只不过用Event修饰

@Entry
@ComponentV2
struct Index {
  @Local msg: string = 'Hello World'

  build() {
    Column() {
      Text('index==' + this.msg)
      son({
        msg: this.msg, updata: () => {
          this.msg = 'hello v2'
        }
      })
    }
    .width('100%')
    .height('100%')
  }
}

@ComponentV2
export struct son {
  @Param msg: string = ''
  @Event updata: () => void = () => {
  }

  build() {
    Column() {
      Text('son==' + this.msg)
        .onClick(() => {
          this.updata()
        })
    }
  }
}

recording.gif

7. @Provider装饰器和@Consumer装饰器:跨组件层级双向同步

7.1 基本概念

@Provider,即数据提供方,其所有的子组件都可以通过@Consumer绑定相同的key来获取@Provider提供的数据。

@Consumer,即数据消费方,可以通过绑定同样的key获取其最近父节点的@Provider的数据,当查找不到@Provider的数据时,使用本地默认值。

@Provider和@Consumer装饰数据类型需要一致。

开发者在使用@Provider和@Consumer时要注意:

  • @Provider和@Consumer强依赖自定义组件层级,@Consumer所在组件会由于其父组件的不同,而被初始化为不同的值。
  • @Provider和@Consumer相当于把组件粘合在一起了,从组件独立角度,要减少使用@Provider和@Consumer。

7.2 基本使用

@Entry
@ComponentV2
struct Parent {
  @Provider() str: string = 'hello';

  build() {
    Column() {
      Button(this.str)
        .onClick(() => {
          this.str += '0';
        })
      Child()
    }
  }
}


@ComponentV2
struct Child {
  @Consumer() str: string = 'world';

  build() {
    Column() {
      Button(this.str)
        .onClick(() => {
          this.str += '0';
        })
    }
  }
}

recording.gif

注意事项

key值不同无法建立联系

image.png

recording.gif

7.3 V2@Provider和@Consumer与V1@Provide和@Consume对比

能力V2装饰器@Provider和@ConsumerV1装饰器@Provide和@Consume
@Consume(r)允许本地初始化,当找不到@Provider的时候使用本地默认值。禁止本地初始化,当找不到对应的的@Provide时候,会抛出异常。
支持类型支持function。不支持function。
观察能力仅能观察自身赋值变化,如果要观察嵌套场景,配合@Trace一起使用。观察第一层变化,如果要观察嵌套场景,配合@Observed和@ObjectLink一起使用。
alias和属性名alias是唯一匹配的key,如果缺省alias,则默认属性名为alias。alias和属性名都为key,优先匹配alias,匹配不到可以匹配属性名。
@Provide(r) 从父组件初始化禁止。允许。
@Provide(r)支持重载默认开启,即@Provider可以重名,@Consumer向上查找最近的@Provider。默认关闭,即在组件树上不允许有同名@Provide。如果需要重载,则需要配置allowOverride。

8. @Monitor装饰器:状态变量修改监听

8.1 基本概念

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

可以同时监听多个状态变量

可以监听多层

8.2 @Monitor装饰器与@Watch装饰器的区别

@Watch@Monitor
参数回调方法名监听状态变量名、属性名
监听目标数只能监听单个状态变量能同时监听多个状态变量
监听能力跟随状态变量观察能力(一层)跟随状态变量观察能力(深层)
能否获取变化前的值不能获取变化前的值能获取变化前的值
监听条件监听对象为状态变量监听对象为状态变量或为@Trace装饰的类成员属性
使用限制仅能在@Component装饰的自定义组件中使用能在@ComponentV2装饰的自定义组件中使用,也能在@ObservedV2装饰的类中使用

8.3 基本使用

@ObservedV2
class Person {
  id: number
  @Trace name: string
  @Trace age: number

  constructor(id: number, name: string, age: number) {
    this.id = id
    this.name = name
    this.age = age
  }
  @Monitor('id')
  onIdChange(monitor: IMonitor) {
    console.log(`id change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
  }

  @Monitor('name')
  onNameChange(monitor: IMonitor) {
    console.log(`name change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
  }

  @Monitor('age')
  onAgeChange(monitor: IMonitor) {
    console.log(`age change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
  }
}

@Entry
@ComponentV2
struct Index {
  @Local message: string = "Hello World";
  @Local name: string = "Tom";
  @Local age: number = 24;
  person: Person = new Person(1, '张三', 18)

  @Monitor("message", "name")
  onStrChange(monitor: IMonitor) {
    monitor.dirty.forEach((path: string) => {
      console.log(`${path} changed from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`)
    })
  }

  build() {
    Column() {
      Text(this.message)
      Text(this.name)
      Text(this.age.toString())
      Button("改变单层")
        .onClick(() => {
          this.message += "!";
          this.name = "Jack";
        })
      Divider()
      Column() {
        Text(this.person.id.toString())
        Text(this.person.name)
        Text(this.person.age.toString())
        Button("改变对象")
          .onClick(() => {
            this.person.id += 1;
            this.person.name = "李四";
            this.person.age += 1;
          })
      }

    }
  }
}

尤其注意监听对象时监听器的位置,应该写在类中

改变前

image.png

改变后

image.png

image.png

虽然对象的id属性也设置了监听,但是没有用@Trace修饰,故监听不到变化