HarmonyOS状态管理----V2

@ObservedV2和@Trace

为了增强状态管理框架对类对象中属性的观测能力,开发者可以使用@ObservedV2装饰器和@Trace装饰器装饰类以及类中的属性。
使用@ObservedV2装饰的类中被@Trace装饰的属性具有被观测变化的能力,当该属性值变化时,会触发该属性绑定的UI组件刷新。

@ObservedV2
class Son {
  @Trace age: number = 100;
}
class Father {
  son: Son = new Son();
}
@Entry
@ComponentV2
struct Index {
  father: Father = new Father();

  build() {
    Column() {
      // 当点击改变age时,Text组件会刷新
      Text(`${this.father.son.age}`)
        .onClick(() => {
          this.father.son.age++;
        })
    }
  }
}
@ObservedV2
class Father {
  @Trace name: string = "Tom";
}
class Son extends Father {
}
@Entry
@ComponentV2
struct Index {
  son: Son = new Son();

  build() {
    Column() {
      // 当点击改变name时,Text组件会刷新
      Text(`${this.son.name}`)
        .onClick(() => {
          this.son.name = "Jack";
        })
    }
  }
}
@ObservedV2
class Manager {
  @Trace static count: number = 1;
}
@Entry
@ComponentV2
struct Index {
  build() {
    Column() {
      // 当点击改变count时,Text组件会刷新
      Text(`${Manager.count}`)
        .onClick(() => {
          Manager.count++;
        })
    }
  }
}

@ComponentV2

为了在自定义组件中使用V2版本状态变量装饰器的能力,开发者可以使用@ComponentV2装饰器装饰自定义组件。
● 在@ComponentV2装饰的自定义组件中,开发者仅可以使用全新的状态变量装饰器,包括@Local、@Param、@Once、@Event、@Provider、@Consumer等。
● @ComponentV2装饰的自定义组件暂不支持组件复用、LocalStorage等现有自定义组件的能力。
● 无法同时使用@ComponentV2与@Component装饰同一个struct结构。
● @ComponentV2支持一个可选的boolean类型参数freezeWhenInactive,来实现组件冻结功能。
@Local
@Local表示组件内部的状态,使得自定义组件内部的变量具有观测变化的能力:
● 被@Local装饰的变量无法从外部初始化,因此必须在组件内部进行初始化。
● 当被@Local装饰的变量变化时,会刷新使用该变量的组件。
● @Local支持观测number、boolean、string、Object、class等基本类型以及Array、Set、Map、Date等内嵌类型。
● @Local的观测能力仅限于被装饰的变量本身。当装饰简单类型时,能够观测到对变量的赋值;当装饰对象类型时,仅能观测到对对象整体的赋值;当装饰数组类型时,能观测到数组整体以及数组元素项的变化;当装饰Array、Set、Map、Date等内嵌类型时,可以观测到通过API调用带来的变化。详见观察变化。
● @Local支持null、undefined以及联合类型。
注意,@Local无法和@Observed装饰的类实例对象混用。

class RawObject {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}
@ObservedV2
class ObservedObject {
  @Trace name: string;
  constructor(name: string) {
    this.name = name;
  }
}
@Entry
@ComponentV2
struct Index {
  @Local rawObject: RawObject = new RawObject("rawObject");
  @Local observedObject: ObservedObject = new ObservedObject("observedObject");
  build() {
    Column() {
      Text(`${this.rawObject.name}`)
      Text(`${this.observedObject.name}`)
      Button("change object")
        .onClick(() => {
          // 对类对象整体的修改均能观察到
          this.rawObject = new RawObject("new rawObject");
          this.observedObject = new ObservedObject("new observedObject");
      })
      Button("change name")
        .onClick(() => {
          // @Local不具备观察类对象属性的能力,因此对rawObject.name的修改无法观察到
          this.rawObject.name = "new rawObject name";
          // 由于ObservedObject的name属性被@Trace装饰,因此对observedObject.name的修改能被观察到
          this.observedObject.name = "new observedObject name";
      })
    }
  }
}

处理复杂类型嵌套监听,更新UI变化问题

@ObservedV2
class Region {
  @Trace x: number;
  @Trace y: number;
  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }
}
@ObservedV2
class Info {
  @Trace region: Region;
  @Trace name: string;
  constructor(name: string, x: number, y: number) {
    this.name = name;
    this.region = new Region(x, y);
  }
}
@Entry
@ComponentV2
struct Index {
  @Local infoArr: Info[] = [new Info("Ocean", 28, 120), new Info("Mountain", 26, 20)];
  @Local originInfo: Info = new Info("Origin", 0, 0);
  build() {
    Column() {
      ForEach(this.infoArr, (info: Info) => {
        Row() {
          Text(`name: ${info.name}`)
          Text(`region: ${info.region.x}-${info.region.y}`)
        }
      })
      Row() {
          Text(`Origin name: ${this.originInfo.name}`)
          Text(`Origin region: ${this.originInfo.region.x}-${this.originInfo.region.y}`)
      }
      Button("change infoArr item")
        .onClick(() => {
          // 由于属性name被@Trace装饰,所以能够观察到
          this.infoArr[0].name = "Win";
        })
      Button("change originInfo")
        .onClick(() => {
          // 由于变量originInfo被@Local装饰,所以能够观察到
          this.originInfo = new Info("Origin", 100, 100);
        })
      Button("change originInfo region")
        .onClick(() => {
          // 由于属性x、y被@Trace装饰,所以能够观察到
          this.originInfo.region.x = 25;
          this.originInfo.region.y = 25;
        })
    }
  }
}

@Param

● @Param装饰的变量支持本地初始化,但是不允许在组件内部直接修改变量本身。
● 被@Param装饰的变量能够在初始化自定义组件时从外部传入,当数据源也是状态变量时,数据源的修改会同步给@Param。
● @Param可以接受任意类型的数据源,包括普通变量、状态变量、常量、函数返回值等。
● @Param装饰的变量变化时,会刷新该变量关联的组件。
● @Param支持观测number、boolean、string、Object、class等基本类型以及Array、Set、Map、Date等内嵌类型。
● 对于复杂类型如类对象,@Param会接受数据源的引用。在组件内可以修改类对象中的属性,该修改会同步到数据源。

@ObservedV2
class Info {
  @Trace name: string;
  constructor(name: string) {
    this.name = name;
  }
}
@Entry
@ComponentV2
struct Index {
  @Local info: Info = new Info("Tom");
  build() {
    Column() {
      Text(`Parent info.name ${this.info.name}`)
      Button("Parent change info")
        .onClick(() => {
          this.info = new Info("Lucy"); // 父组件更改@Local变量,会同步子组件对应@Param变量
      })
      Child({ info: this.info })
    }
  }
}
@ComponentV2
struct Child {
  @Require @Param info: Info;
  build() {
    Column() {
      Text(`info.name: ${this.info.name}`)
      Button("change info")
        .onClick(() => {
          this.info = new Info("Jack"); //错误用法,不允许在子组件更改@Param变量,编译时报错
        })
      Button("Child change info.name")
        .onClick(() => {
          this.info.name = "Jack"; // 允许在子组件中更改对象中属性
        })
    }
  }
}

@Once
为了实现仅从外部初始化一次、不接受后续同步变化的能力,开发者可以使用@Once装饰器搭配@Param装饰器使用。

@Once装饰器仅在变量初始化时接受外部传入值进行初始化,当后续数据源更改时,不会将修改同步给子组件:
● @Once必须搭配@Param使用,单独使用或搭配其他装饰器使用都是不允许的。
● @Once不影响@Param的观测能力,仅针对数据源的变化做拦截。
● @Once与@Param装饰变量的先后顺序不影响实际功能。
● @Once与@Param搭配使用时,可以在本地修改@Param变量的值。
当@Once搭配@Param使用时,可以解除@Param无法在本地修改的限制,且修改能够触发UI刷新。此时,使用@Param @Once相当于使用@Local,区别在于@Param @Once能够接受外部传入初始化。

@ObservedV2
class Info {
  @Trace name: string;
  constructor(name: string) {
    this.name = name;
  }
}
@ComponentV2
struct Child {
  @Param @Once onceParamNum: number = 0;
  @Param @Once @Require onceParamInfo: Info;

  build() {
    Column() {
      Text(`Child onceParamNum: ${this.onceParamNum}`)
      Text(`Child onceParamInfo: ${this.onceParamInfo.name}`)
      Button("changeOnceParamNum")
        .onClick(() => {
          this.onceParamNum++;
        })
      Button("changeParamInfo")
        .onClick(() => {
          this.onceParamInfo = new Info("Cindy");
        })
    }
  }
}
@Entry
@ComponentV2
struct Index {
  @Local localNum: number = 10;
  @Local localInfo: Info = new Info("Tom");

  build() {
    Column() {
      Text(`Parent localNum: ${this.localNum}`)
      Text(`Parent localInfo: ${this.localInfo.name}`)
      Button("changeLocalNum")
        .onClick(() => {
          this.localNum++;
        })
      Button("changeLocalInfo")
        .onClick(() => {
          this.localInfo = new Info("Cindy");
        })
      Child({
        onceParamNum: this.localNum,
        onceParamInfo: this.localInfo
      })
    }
  }
}

@Event
为了实现子组件向父组件要求更新@Param装饰变量的能力,开发者可以使用@Event装饰器。使用@Event装饰回调方法是一种规范,表明子组件需要传入更新数据源的回调
@Event主要配合@Param实现数据的双向同步。
● @Event装饰的回调方法中参数以及返回值由开发者决定。
● @Event装饰非回调类型的变量不会生效。当@Event没有初始化时,会自动生成一个空的函数作为默认回调。
● 当@Event未被外部初始化,但本地有默认值时,会使用本地默认的函数进行处理。

使用@Event可以更改父组件中变量,当该变量作为子组件@Param变量的数据源时,该变化会同步回子组件的@Param变量。使用@Event修改父组件的值是立刻生效的,但从父组件将变化同步回子组件的过程是异步的,即在调用完@Event的方法后,子组件内的值不会立刻变化。这是因为@Event将子组件值实际的变化能力交由父组件处理,在父组件实际决定如何处理后,将最终值在渲染之前同步回子组件。

@Entry
@ComponentV2
struct Index {
  @Local title: string = "Title One";
  @Local fontColor: Color = Color.Red;

  build() {
    Column() {
      Child({
        title: this.title,
        fontColor: this.fontColor,
        changeFactory: (type: number) => {
          if (type == 1) {
            this.title = "Title One";
            this.fontColor = Color.Red;
          } else if (type == 2) {
            this.title = "Title Two";
            this.fontColor = Color.Green;
          }
        }
      })
    }
  }
}

@ComponentV2
struct Child {
  @Param title: string = '';
  @Param fontColor: Color = Color.Black;
  @Event changeFactory: (x: number) => void = (x: number) => {};

  build() {
    Column() {
      Text(`${this.title}`)
        .fontColor(this.fontColor)
      Button("change to Title Two")
        .onClick(() => {
          this.changeFactory(2);
        })
      Button("change to Title One")
        .onClick(() => {
          this.changeFactory(1);
        })
    }
  }
}

@Provider & @Consumer

@Provider和@Consumer用于跨组件层级数据双向同步,可以使得开发者不用拘泥于组件层级。
@Provider,即数据提供方,其所有的子组件都可以通过@Consumer绑定相同的key来获取@Provider提供的数据。
@Consumer,即数据消费方,可以通过绑定同样的key获取其最近父节点的@Provider的数据,当查找不到@Provider的数据时,使用本地默认值。
开发者在使用@Provider和@Consumer时要注意:
● @Provider和@Consumer强依赖自定义组件层级,@Consumer会因为所在组件的父组件不同,而被初始化为不同的值。
● @Provider和@Consumer相当于把组件粘合在一起了,从组件独立角度,要减少使用@Provider和@Consumer。

@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';
        })
    }
  }
}

@Monitor
为了增强状态管理框架对状态变量变化的监听能力,开发者可以使用@Monitor装饰器对状态变量进行监听。
Monitor只能监听被@Local、@Param、@Provider、@Consumer、@Computed装饰的变量。
同时可以监听多个变量的变化,并能记录变化前和变化后的值。
简单使用

@Entry
@ComponentV2
struct Index {
  @Local message: string = "Hello World";
  @Local name: string = "Tom";
  @Local age: number = 24;
  @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() {
      Button("change string")
        .onClick(() => {
          this.message += "!";
          this.name = "Jack";
      })
    }
  }
}

深层监听

@ObservedV2
class Info {
  @Trace value: number = 50;
}
@ObservedV2
class UIStyle {
  info: Info = new Info();
  @Trace color: Color = Color.Black;
  @Trace fontSize: number = 45;
  @Monitor("info.value")
  onValueChange(monitor: IMonitor) {
    let lastValue: number = monitor.value()?.before as number;
    let curValue: number = monitor.value()?.now as number;
    if (lastValue != 0) {
      let diffPercent: number = (curValue - lastValue) / lastValue;
      if (diffPercent > 0.1) {
        this.color = Color.Red;
        this.fontSize = 50;
      } else if (diffPercent < -0.1) {
        this.color = Color.Green;
        this.fontSize = 40;
      } else {
        this.color = Color.Black;
        this.fontSize = 45;
      }
    }
  }
}
@Entry
@ComponentV2
struct Index {
  textStyle: UIStyle = new UIStyle();
  build() {
    Column() {
      Text(`Important Value: ${this.textStyle.info.value}`)
        .fontColor(this.textStyle.color)
        .fontSize(this.textStyle.fontSize)
      Button("change!")
        .onClick(() => {
          this.textStyle.info.value = Math.floor(Math.random() * 100) + 1;
        })
    }
  }
}

被@Trace和@ObserveredV2装饰的类不能和V1混用。@Trace装饰器与现有状态管理框架的@Track与@State装饰器的能力不同,@Track使class具有属性级更新的能力,但并不具备深度观测的能力;而@State只能观测到对象本身以及第一层的变化,对于多层嵌套场景只能通过封装自定义组件,搭配@Observed和@ObjectLink来实现观测。

关注公众号“极客马拉松” , 发送 “鸿蒙仓库” ,即可获取完成代码。 每周更新鸿蒙相关技术。
vx扫码关注公众号, 发送“鸿蒙仓库”

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值