手把手教你做网站 怎么注册域名,宁波网站建设优化找哪家,WordPress点击头像,室内设计心得体会800字上文所述的装饰器仅能观察到第一层的变化#xff0c;但是在实际应用开发中#xff0c;应用会根据开发需要#xff0c;封装自己的数据模型。对于多层嵌套的情况#xff0c;比如二维数组#xff0c;或者数组项class#xff0c;或者class的属性是class#xff0c;他们的第二…上文所述的装饰器仅能观察到第一层的变化但是在实际应用开发中应用会根据开发需要封装自己的数据模型。对于多层嵌套的情况比如二维数组或者数组项class或者class的属性是class他们的第二层的属性变化是无法观察到的。这就引出了Observed/ObjectLink装饰器。 说明 从API version 9开始这两个装饰器支持在ArkTS卡片中使用。 概述
ObjectLink和Observed类装饰器用于在涉及嵌套对象或数组的场景中进行双向数据同步 被Observed装饰的类可以被观察到属性的变化 子组件中ObjectLink装饰器装饰的状态变量用于接收Observed装饰的类的实例和父组件中对应的状态变量建立双向数据绑定。这个实例可以是数组中的被Observed装饰的项或者是class object中的属性这个属性同样也需要被Observed装饰。 单独使用Observed是没有任何作用的需要搭配ObjectLink或者 Prop 使用。
限制条件 使用Observed装饰class会改变class原始的原型链Observed和其他类装饰器装饰同一个class可能会带来问题。 ObjectLink装饰器不能在Entry装饰的自定义组件中使用。
装饰器说明
Observed类装饰器说明装饰器参数无类装饰器装饰class。需要放在class的定义前使用new创建类对象。
ObjectLink变量装饰器说明装饰器参数无允许装饰的变量类型必须为被Observed装饰的class实例必须指定类型。不支持简单类型可以使用 Prop 。支持继承Date、Array的class实例API11及以上支持继承Map、Set的class实例。示例见 观察变化 。API11及以上支持Observed装饰类和undefined或null组成的联合类型比如ClassA | ClassB, ClassA | undefined 或者 ClassA | null, 示例见 ObjectLink支持联合类型 。ObjectLink的属性是可以改变的但是变量的分配是不允许的也就是说这个装饰器装饰变量是只读的不能被改变。被装饰变量的初始值不允许。
ObjectLink装饰的数据为可读示例。
// 允许ObjectLink装饰的数据属性赋值
this.objLink.a ...
// 不允许ObjectLink装饰的数据自身赋值
this.objLink ...说明 ObjectLink装饰的变量不能被赋值如果要使用赋值操作请使用 Prop 。 Prop装饰的变量和数据源的关系是是单向同步Prop装饰的变量在本地拷贝了数据源所以它允许本地更改如果父组件中的数据源有更新Prop装饰的变量本地的修改将被覆盖 ObjectLink装饰的变量和数据源的关系是双向同步ObjectLink装饰的变量相当于指向数据源的指针。禁止对ObjectLink装饰的变量赋值如果一旦发生ObjectLink装饰的变量的赋值则同步链将被打断。因为ObjectLink装饰的变量通过数据源Object引用来初始化。对于实现双向数据同步的ObjectLink赋值相当于更新父组件中的数组项或者class的属性TypeScript/JavaScript不能实现会发生运行时报错。 变量的传递/访问规则说明
ObjectLink传递/访问说明从父组件初始化必须指定。初始化ObjectLink装饰的变量必须同时满足以下场景- 类型必须是Observed装饰的class。- 初始化的数值需要是数组项或者class的属性。- 同步源的class或者数组必须是StateLinkProvideConsume或者ObjectLink装饰的数据。同步源是数组项的示例请参考 对象数组 。初始化的class的示例请参考 嵌套对象 。与源对象同步双向。可以初始化子组件允许可用于初始化常规变量、State、Link、Prop、Provide
图1 初始化规则图示 观察变化和行为表现
观察变化
Observed装饰的类如果其属性为非简单类型比如class、Object或者数组也需要被Observed装饰否则将观察不到其属性的变化。
class ClassA {public c: number;constructor(c: number) {this.c c;}
}Observed
class ClassB {public a: ClassA;public b: number;constructor(a: ClassA, b: number) {this.a a;this.b b;}
}以上示例中ClassB被Observed装饰其成员变量的赋值的变化是可以被观察到的但对于ClassA没有被Observed装饰其属性的修改不能被观察到。
ObjectLink b: ClassB// 赋值变化可以被观察到
this.b.a new ClassA(5)
this.b.b 5// ClassA没有被Observed装饰其属性的变化观察不到
this.b.a.c 5ObjectLinkObjectLink只能接收被Observed装饰class的实例可以观察到 其属性的数值的变化其中属性是指Object.keys(observedObject)返回的所有属性示例请参考 嵌套对象 。 如果数据源是数组则可以观察到数组item的替换如果数据源是class可观察到class的属性的变化示例请参考 对象数组 。
继承Date的class时可以观察到Date整体的赋值同时可通过调用Date的接口setFullYear, setMonth, setDate, setHours, setMinutes, setSeconds, setMilliseconds, setTime, setUTCFullYear, setUTCMonth, setUTCDate, setUTCHours, setUTCMinutes, setUTCSeconds, setUTCMilliseconds 更新Date的属性。
Observed
class DateClass extends Date {constructor(args: number | string) {super(args)}
}Observed
class ClassB {public a: DateClass;constructor(a: DateClass) {this.a a;}
}Component
struct ViewA {label: string date;ObjectLink a: DateClass;build() {Column() {Button(child increase the day by 1).onClick(() {this.a.setDate(this.a.getDate() 1);})DatePicker({start: new Date(1970-1-1),end: new Date(2100-1-1),selected: this.a})}}
}Entry
Component
struct ViewB {State b: ClassB new ClassB(new DateClass(2023-1-1));build() {Column() {ViewA({ label: date, a: this.b.a })Button(parent update the new date).onClick(() {this.b.a new DateClass(2023-07-07);})Button(ViewB: this.b new ClassB(new DateClass(2023-08-20))).onClick(() {this.b new ClassB(new DateClass(2023-08-20));})}}
}继承Map的class时可以观察到Map整体的赋值同时可通过调用Map的接口set, clear, delete 更新Map的值。
继承Set的class时可以观察到Set整体的赋值同时可通过调用Set的接口add, clear, delete 更新Set的值。详见继承Set类。
框架行为 初始渲染 Observed装饰的class的实例会被不透明的代理对象包装代理了class上的属性的setter和getter方法子组件中ObjectLink装饰的从父组件初始化接收被Observed装饰的class的实例ObjectLink的包装类会将自己注册给Observed class。 属性更新当Observed装饰的class属性改变时会走到代理的setter和getter然后遍历依赖它的ObjectLink包装类通知数据更新。
使用场景
嵌套对象
以下是嵌套类对象的数据结构。 说明 NextID是用来在 ForEach循环渲染 过程中为每个数组元素生成一个唯一且持久的键值用于标识对应的组件。 // objectLinkNestedObjects.ets
let NextID: number 1;Observed
class ClassA {public id: number;public c: number;constructor(c: number) {this.id NextID;this.c c;}
}Observed
class ClassB {public a: ClassA;constructor(a: ClassA) {this.a a;}
}Observed
class ClassD {public c: ClassC;constructor(c: ClassC) {this.c c;}
}Observed
class ClassC extends ClassA {public k: number;constructor(k: number) {// 调用父类方法对k进行处理super(k);this.k k;}
}以下组件层次结构呈现的是嵌套类对象的数据结构。
Component
struct ViewC {label: string ViewC1;ObjectLink c: ClassC;build() {Row() {Column() {Text(ViewC [${this.label}] this.a.c ${this.c.c}).fontColor(#ffffffff).backgroundColor(#ff3fc4c4).height(50).borderRadius(25)Button(ViewC: this.c.c add 1).backgroundColor(#ff7fcf58).onClick(() {this.c.c 1;console.log(this.c.c: this.c.c)})}.width(300)}}
}Entry
Component
struct ViewB {State b: ClassB new ClassB(new ClassA(0));State child: ClassD new ClassD(new ClassC(0));build() {Column() {ViewC({ label: ViewC #3,c: this.child.c })Button(ViewC: this.child.c.c add 10).backgroundColor(#ff7fcf58).onClick(() {this.child.c.c 10console.log(this.child.c.c: this.child.c.c)})}}
}被Observed装饰的ClassC类可以观测到继承基类的属性的变化。
ViewB中的事件句柄 this.child.c new ClassA(0) 和this.b new ClassB(new ClassA(0)) 对State装饰的变量b和其属性的修改。 this.child.c.c … 该变化属于第二层的变化State无法观察到第二层的变化但是ClassA被Observed装饰ClassA的属性c的变化可以被ObjectLink观察到。
ViewC中的事件句柄 this.c.c 1对ObjectLink变量a的修改将触发Button组件的刷新。ObjectLink和Prop不同ObjectLink不拷贝来自父组件的数据源而是在本地构建了指向其数据源的引用。 ObjectLink变量是只读的this.a new ClassA(…)是不允许的因为一旦赋值操作发生指向数据源的引用将被重置同步将被打断。
对象数组
对象数组是一种常用的数据结构。以下示例展示了数组对象的用法。
let NextID: number 1;Observed
class ClassA {public id: number;public c: number;constructor(c: number) {this.id NextID;this.c c;}
}Component
struct ViewA {// 子组件ViewA的ObjectLink的类型是ClassAObjectLink a: ClassA;label: string ViewA1;build() {Row() {Button(ViewA [${this.label}] this.a.c ${this.a ? this.a.c : undefined}).onClick(() {this.a.c 1;})}}
}Entry
Component
struct ViewB {// ViewB中有State装饰的ClassA[]State arrA: ClassA[] [new ClassA(0), new ClassA(0)];build() {Column() {ForEach(this.arrA,(item: ClassA) {ViewA({ label: #${item.id}, a: item })},(item: ClassA): string item.id.toString())// 使用State装饰的数组的数组项初始化ObjectLink其中数组项是被Observed装饰的ClassA的实例ViewA({ label: ViewA this.arrA[first], a: this.arrA[0] })ViewA({ label: ViewA this.arrA[last], a: this.arrA[this.arrA.length-1] })Button(ViewB: reset array).onClick(() {this.arrA [new ClassA(0), new ClassA(0)];})Button(ViewB: push).onClick(() {this.arrA.push(new ClassA(0))})Button(ViewB: shift).onClick(() {if (this.arrA.length 0) {this.arrA.shift()} else {console.log(length 0)}})Button(ViewB: chg item property in middle).onClick(() {this.arrA[Math.floor(this.arrA.length / 2)].c 10;})Button(ViewB: chg item property in middle).onClick(() {this.arrA[Math.floor(this.arrA.length / 2)] new ClassA(11);})}}
}this.arrA[Math.floor(this.arrA.length/2)] new ClassA(…) 该状态变量的改变触发2次更新 ForEach数组项的赋值导致ForEach的itemGenerator被修改因此数组项被识别为有更改ForEach的item builder将执行创建新的ViewA组件实例。ViewA({ label: ViewA this.arrA[last], a: this.arrA[this.arrA.length-1] })上述更改改变了数组中第二个元素所以绑定this.arrA[1]的ViewA将被更新。 this.arrA.push(new ClassA(0)) 将触发2次不同效果的更新 ForEach新添加的ClassA对象对于ForEach是未知的 itemGenerator ForEach的item builder将执行创建新的ViewA组件实例。ViewA({ label: ViewA this.arrA[last], a: this.arrA[this.arrA.length-1] })数组的最后一项有更改因此引起第二个ViewA的实例的更改。对于ViewA({ label: ViewA this.arrA[first], a: this.arrA[0] })数组的更改并没有触发一个数组项更改的改变所以第一个ViewA不会刷新。 this.arrA[Math.floor(this.arrA.length/2)].cState无法观察到第二层的变化但是ClassA被Observed装饰ClassA的属性的变化将被ObjectLink观察到。
二维数组
使用Observed观察二维数组的变化。可以声明一个被Observed装饰的继承Array的子类。
Observed
class StringArray extends ArrayString {
}使用new StringArray()来构造StringArray的实例new运算符使得Observed生效Observed观察到StringArray的属性变化。
声明一个从Array扩展的类class StringArray extends ArrayString {}并创建StringArray的实例。Observed装饰的类需要使用new运算符来构建class实例。
Observed
class StringArray extends ArrayString {
}Component
struct ItemPage {ObjectLink itemArr: StringArray;build() {Row() {Text(ItemPage).width(100).height(100)ForEach(this.itemArr,(item: string | Resource) {Text(item).width(100).height(100)},(item: string) item)}}
}Entry
Component
struct IndexPage {State arr: ArrayStringArray [new StringArray(), new StringArray(), new StringArray()];build() {Column() {ItemPage({ itemArr: this.arr[0] })ItemPage({ itemArr: this.arr[1] })ItemPage({ itemArr: this.arr[2] })Divider()ForEach(this.arr,(itemArr: StringArray) {ItemPage({ itemArr: itemArr })},(itemArr: string) itemArr[0])Divider()Button(update).onClick(() {console.error(Update all items in arr);if ((this.arr[0] as ArrayString)[0] ! undefined) {// 正常情况下需要有一个真实的ID来与ForEach一起使用但此处没有// 因此需要确保推送的字符串是唯一的。this.arr[0].push(${this.arr[0].slice(-1).pop()}${this.arr[0].slice(-1).pop()});this.arr[1].push(${this.arr[1].slice(-1).pop()}${this.arr[1].slice(-1).pop()});this.arr[2].push(${this.arr[2].slice(-1).pop()}${this.arr[2].slice(-1).pop()});} else {this.arr[0].push(Hello);this.arr[1].push(World);this.arr[2].push(!);}})}}
}继承Map类 说明 从API version 11开始ObjectLink支持Observed装饰Map类型和继承Map类的类型。 在下面的示例中myMap类型为MyMapnumber, string点击Button改变myMap的属性视图会随之刷新。
Observed
class ClassA {public a: MyMapnumber, string;constructor(a: MyMapnumber, string) {this.a a;}
}Observed
export class MyMapK, V extends MapK, V {public name: string;constructor(name?: string, args?: [K, V][]) {super(args);this.name name ? name : My Map;}getName() {return this.name;}
}Entry
Component
struct MapSampleNested {State message: ClassA new ClassA(new MyMap(myMap, [[0, a], [1, b], [3, c]]));build() {Row() {Column() {MapSampleNestedChild({ myMap: this.message.a })}.width(100%)}.height(100%)}
}Component
struct MapSampleNestedChild {ObjectLink myMap: MyMapnumber, stringbuild() {Row() {Column() {ForEach(Array.from(this.myMap.entries()), (item: [number, string]) {Text(${item[0]}).fontSize(30)Text(${item[1]}).fontSize(30)Divider()})Button(set new one).onClick(() {this.myMap.set(4, d)})Button(clear).onClick(() {this.myMap.clear()})Button(replace the first one).onClick(() {this.myMap.set(0, aa)})Button(delete the first one).onClick(() {this.myMap.delete(0)})}.width(100%)}.height(100%)}
}继承Set类 说明 从API version 11开始ObjectLink支持Observed装饰Set类型和继承Set类的类型。 在下面的示例中mySet类型为MySetnumber点击Button改变mySet的属性视图会随之刷新。
Observed
class ClassA {public a: MySetnumber;constructor(a: MySetnumber) {this.a a;}
}Observed
export class MySetT extends SetT {public name: string;constructor(name?: string, args?: T[]) {super(args);this.name name ? name : My Set;}getName() {return this.name;}
}Entry
Component
struct SetSampleNested {State message: ClassA new ClassA(new MySet(Set, [0, 1, 2, 3, 4]));build() {Row() {Column() {SetSampleNestedChild({ mySet: this.message.a })}.width(100%)}.height(100%)}
}Component
struct SetSampleNestedChild {ObjectLink mySet: MySetnumberbuild() {Row() {Column() {ForEach(Array.from(this.mySet.entries()), (item: number) {Text(${item}).fontSize(30)Divider()})Button(set new one).onClick(() {this.mySet.add(5)})Button(clear).onClick(() {this.mySet.clear()})Button(delete the first one).onClick(() {this.mySet.delete(0)})}.width(100%)}.height(100%)}
}ObjectLink支持联合类型
ObjectLink支持Observed装饰类和undefined或null组成的联合类型在下面的示例中count类型为ClassA | ClassB | undefined点击父组件Page2中的Button改变count的属性或者类型Child中也会对应刷新。
Observed
class ClassA {public a: number;constructor(a: number) {this.a a;}
}Observed
class ClassB {public b: number;constructor(b: number) {this.b b;}
}Entry
Component
struct Page2 {State count: ClassA | ClassB | undefined new ClassA(10)build() {Column() {Child({ count: this.count })Button(change count property).onClick(() {// 判断count的类型做属性的更新if (this.count instanceof ClassA) {this.count.a 1} else if (this.count instanceof ClassB) {this.count.b 1} else {console.info(count is undefined, cannot change property)}})Button(change count to ClassA).onClick(() {// 赋值为ClassA的实例this.count new ClassA(100)})Button(change count to ClassB).onClick(() {// 赋值为ClassA的实例this.count new ClassB(100)})Button(change count to undefined).onClick(() {// 赋值为undefinedthis.count undefined})}.width(100%)}
}Component
struct Child {ObjectLink count: ClassA | ClassB | undefinedbuild() {Column() {Text(count is instanceof ${this.count instanceof ClassA ? ClassA : this.count instanceof ClassB ? ClassB : undefined}).fontSize(30)Text(counts property is ${this.count instanceof ClassA ? this.count.a : this.count?.b}).fontSize(15)}.width(100%)}
}常见问题
在子组件中给ObjectLink装饰的变量赋值
在子组件中给ObjectLink装饰的变量赋值是不允许的。
【反例】
Observed
class ClassA {public c: number 0;constructor(c: number) {this.c c;}
}Component
struct ObjectLinkChild {ObjectLink testNum: ClassA;build() {Text(ObjectLinkChild testNum ${this.testNum.c}).onClick(() {// ObjectLink不能被赋值this.testNum new ClassA(47);})}
}Entry
Component
struct Parent {State testNum: ClassA[] [new ClassA(1)];build() {Column() {Text(Parent testNum ${this.testNum[0].c}).onClick(() {this.testNum[0].c 1;})ObjectLinkChild({ testNum: this.testNum[0] })}}
}点击ObjectLinkChild给ObjectLink装饰的变量赋值
this.testNum new ClassA(47); 这是不允许的对于实现双向数据同步的ObjectLink赋值相当于要更新父组件中的数组项或者class的属性这个对于 TypeScript/JavaScript是不能实现的。框架对于这种行为会发生运行时报错。
【正例】
Observed
class ClassA {public c: number 0;constructor(c: number) {this.c c;}
}Component
struct ObjectLinkChild {ObjectLink testNum: ClassA;build() {Text(ObjectLinkChild testNum ${this.testNum.c}).onClick(() {// 可以对ObjectLink装饰对象的属性赋值this.testNum.c 47;})}
}Entry
Component
struct Parent {State testNum: ClassA[] [new ClassA(1)];build() {Column() {Text(Parent testNum ${this.testNum[0].c}).onClick(() {this.testNum[0].c 1;})ObjectLinkChild({ testNum: this.testNum[0] })}}
}基础嵌套对象属性更改失效
在应用开发中有很多嵌套对象场景例如开发者更新了某个属性但UI没有进行对应的更新。
每个装饰器都有自己可以观察的能力并不是所有的改变都可以被观察到只有可以被观察到的变化才会进行UI更新。Observed装饰器可以观察到嵌套对象的属性变化其他装饰器仅能观察到第二层的变化。
【反例】
下面的例子中一些UI组件并不会更新。
class ClassA {a: number;constructor(a: number) {this.a a;}getA(): number {return this.a;}setA(a: number): void {this.a a;}
}class ClassC {c: number;constructor(c: number) {this.c c;}getC(): number {return this.c;}setC(c: number): void {this.c c;}
}class ClassB extends ClassA {b: number 47;c: ClassC;constructor(a: number, b: number, c: number) {super(a);this.b b;this.c new ClassC(c);}getB(): number {return this.b;}setB(b: number): void {this.b b;}getC(): number {return this.c.getC();}setC(c: number): void {return this.c.setC(c);}
}Entry
Component
struct MyView {State b: ClassB new ClassB(10, 20, 30);build() {Column({ space: 10 }) {Text(a: ${this.b.a})Button(Change ClassA.a).onClick(() {this.b.a 1;})Text(b: ${this.b.b})Button(Change ClassB.b).onClick(() {this.b.b 1;})Text(c: ${this.b.c.c})Button(Change ClassB.ClassC.c).onClick(() {// 点击时上面的Text组件不会刷新this.b.c.c 1;})}}
}最后一个Text组件Text(‘c: ${this.b.c.c}’)当点击该组件时UI不会刷新。 因为State b : ClassB 只能观察到this.b属性的变化比如this.b.a, this.b.b 和this.b.c的变化但是无法观察嵌套在属性中的属性即this.b.c.c属性c是内嵌在b中的对象classC的属性。 为了观察到嵌套于内部的ClassC的属性需要做如下改变 构造一个子组件用于单独渲染ClassC的实例。 该子组件可以使用ObjectLink c : ClassC或Prop c : ClassC。通常会使用ObjectLink除非子组件需要对其ClassC对象进行本地修改。嵌套的ClassC必须用Observed装饰。当在ClassB中创建ClassC对象时本示例中的ClassB(10, 20, 30)它将被包装在ES6代理中当ClassC属性更改时this.b.c.c 1该代码将修改通知到ObjectLink变量。
【正例】
以下示例使用Observed/ObjectLink来观察嵌套对象的属性更改。
class ClassA {a: number;constructor(a: number) {this.a a;}getA(): number {return this.a;}setA(a: number): void {this.a a;}
}Observed
class ClassC {c: number;constructor(c: number) {this.c c;}getC(): number {return this.c;}setC(c: number): void {this.c c;}
}class ClassB extends ClassA {b: number 47;c: ClassC;constructor(a: number, b: number, c: number) {super(a);this.b b;this.c new ClassC(c);}getB(): number {return this.b;}setB(b: number): void {this.b b;}getC(): number {return this.c.getC();}setC(c: number): void {return this.c.setC(c);}
}Component
struct ViewClassC {ObjectLink c: ClassC;build() {Column({ space: 10 }) {Text(c: ${this.c.getC()})Button(Change C).onClick(() {this.c.setC(this.c.getC() 1);})}}
}Entry
Component
struct MyView {State b: ClassB new ClassB(10, 20, 30);build() {Column({ space: 10 }) {Text(a: ${this.b.a})Button(Change ClassA.a).onClick(() {this.b.a 1;})Text(b: ${this.b.b})Button(Change ClassB.b).onClick(() {this.b.b 1;})ViewClassC({ c: this.b.c }) // Text(c: ${this.b.c.c})的替代写法Button(Change ClassB.ClassC.c).onClick(() {this.b.c.c 1;})}}
}复杂嵌套对象属性更改失效
【反例】
以下示例创建了一个带有ObjectLink装饰变量的子组件用于渲染一个含有嵌套属性的ParentCounter用Observed装饰嵌套在ParentCounter中的SubCounter。
let nextId 1;
Observed
class SubCounter {counter: number;constructor(c: number) {this.counter c;}
}
Observed
class ParentCounter {id: number;counter: number;subCounter: SubCounter;incrCounter() {this.counter;}incrSubCounter(c: number) {this.subCounter.counter c;}setSubCounter(c: number): void {this.subCounter.counter c;}constructor(c: number) {this.id nextId;this.counter c;this.subCounter new SubCounter(c);}
}
Component
struct CounterComp {ObjectLink value: ParentCounter;build() {Column({ space: 10 }) {Text(${this.value.counter}).fontSize(25).onClick(() {this.value.incrCounter();})Text(${this.value.subCounter.counter}).onClick(() {this.value.incrSubCounter(1);})Divider().height(2)}}
}
Entry
Component
struct ParentComp {State counter: ParentCounter[] [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)];build() {Row() {Column() {CounterComp({ value: this.counter[0] })CounterComp({ value: this.counter[1] })CounterComp({ value: this.counter[2] })Divider().height(5)ForEach(this.counter,(item: ParentCounter) {CounterComp({ value: item })},(item: ParentCounter) item.id.toString())Divider().height(5)// 第一个点击事件Text(Parent: incr counter[0].counter).fontSize(20).height(50).onClick(() {this.counter[0].incrCounter();// 每次触发时自增10this.counter[0].incrSubCounter(10);})// 第二个点击事件Text(Parent: set.counter to 10).fontSize(20).height(50).onClick(() {// 无法将value设置为10UI不会刷新this.counter[0].setSubCounter(10);})Text(Parent: reset entire counter).fontSize(20).height(50).onClick(() {this.counter [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)];})}}}
}对于Text(‘Parent: incr counter[0].counter’)的onClick事件this.counter[0].incrSubCounter(10)调用incrSubCounter方法使SubCounter的counter值增加10UI同步刷新。
但是在Text(‘Parent: set.counter to 10’)的onClick中调用this.counter[0].setSubCounter(10)SubCounter的counter值却无法重置为10。
incrSubCounter和setSubCounter都是同一个SubCounter的函数。在第一个点击处理时调用incrSubCounter可以正确更新UI而第二个点击处理调用setSubCounter时却没有更新UI。实际上incrSubCounter和setSubCounter两个函数都不能触发Text(‘${this.value.subCounter.counter}’)的更新因为ObjectLink value : ParentCounter仅能观察其代理ParentCounter的属性对于this.value.subCounter.counter是SubCounter的属性无法观察到嵌套类的属性。
但是第一个click事件调用this.counter[0].incrCounter()将CounterComp自定义组件中ObjectLink value: ParentCounter标记为已更改。此时触发Text(‘${this.value.subCounter.counter}’)的更新。 如果在第一个点击事件中删除this.counter[0].incrCounter()也无法更新UI。
【正例】
对于上述问题为了直接观察SubCounter中的属性以便this.counter[0].setSubCounter(10)操作有效可以利用下面的方法
ObjectLink valueParentCounter new ParentCounter(0);
ObjectLink subValueSubCounter new SubCounter(0);该方法使得ObjectLink分别代理了ParentCounter和SubCounter的属性这样对于这两个类的属性的变化都可以观察到即都会对UI视图进行刷新。即使删除了上面所说的this.counter[0].incrCounter()UI也会进行正确的刷新。
该方法可用于实现“两个层级”的观察即外部对象和内部嵌套对象的观察。但是该方法只能用于ObjectLink装饰器无法作用于PropProp通过深拷贝传入对象。详情参考Prop与ObjectLink的差异。
let nextId 1;Observed
class SubCounter {counter: number;constructor(c: number) {this.counter c;}
}Observed
class ParentCounter {id: number;counter: number;subCounter: SubCounter;incrCounter() {this.counter;}incrSubCounter(c: number) {this.subCounter.counter c;}setSubCounter(c: number): void {this.subCounter.counter c;}constructor(c: number) {this.id nextId;this.counter c;this.subCounter new SubCounter(c);}
}Component
struct CounterComp {ObjectLink value: ParentCounter;build() {Column({ space: 10 }) {Text(${this.value.counter}).fontSize(25).onClick(() {this.value.incrCounter();})CounterChild({ subValue: this.value.subCounter })Divider().height(2)}}
}Component
struct CounterChild {ObjectLink subValue: SubCounter;build() {Text(${this.subValue.counter}).onClick(() {this.subValue.counter 1;})}
}Entry
Component
struct ParentComp {State counter: ParentCounter[] [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)];build() {Row() {Column() {CounterComp({ value: this.counter[0] })CounterComp({ value: this.counter[1] })CounterComp({ value: this.counter[2] })Divider().height(5)ForEach(this.counter,(item: ParentCounter) {CounterComp({ value: item })},(item: ParentCounter) item.id.toString())Divider().height(5)Text(Parent: reset entire counter).fontSize(20).height(50).onClick(() {this.counter [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)];})Text(Parent: incr counter[0].counter).fontSize(20).height(50).onClick(() {this.counter[0].incrCounter();this.counter[0].incrSubCounter(10);})Text(Parent: set.counter to 10).fontSize(20).height(50).onClick(() {this.counter[0].setSubCounter(10);})}}}
}Prop与ObjectLink的差异
在下面的示例代码中ObjectLink装饰的变量是对数据源的引用即在this.value.subValue和this.subValue都是同一个对象的不同引用所以在点击CounterComp的click handler改变this.value.subCounter.counterthis.subValue.counter也会改变对应的组件Text(this.subValue.counter: ${this.subValue.counter})会刷新。
let nextId 1;Observed
class SubCounter {counter: number;constructor(c: number) {this.counter c;}
}Observed
class ParentCounter {id: number;counter: number;subCounter: SubCounter;incrCounter() {this.counter;}incrSubCounter(c: number) {this.subCounter.counter c;}setSubCounter(c: number): void {this.subCounter.counter c;}constructor(c: number) {this.id nextId;this.counter c;this.subCounter new SubCounter(c);}
}Component
struct CounterComp {ObjectLink value: ParentCounter;build() {Column({ space: 10 }) {CountChild({ subValue: this.value.subCounter })Text(this.value.counterincrease 7 ).fontSize(30).onClick(() {// click handler, Text(this.subValue.counter: ${this.subValue.counter}) will updatethis.value.incrSubCounter(7);})Divider().height(2)}}
}Component
struct CountChild {ObjectLink subValue: SubCounter;build() {Text(this.subValue.counter: ${this.subValue.counter}).fontSize(30)}
}Entry
Component
struct ParentComp {State counter: ParentCounter[] [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)];build() {Row() {Column() {CounterComp({ value: this.counter[0] })CounterComp({ value: this.counter[1] })CounterComp({ value: this.counter[2] })Divider().height(5)ForEach(this.counter,(item: ParentCounter) {CounterComp({ value: item })},(item: ParentCounter) item.id.toString())Divider().height(5)Text(Parent: reset entire counter).fontSize(20).height(50).onClick(() {this.counter [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)];})Text(Parent: incr counter[0].counter).fontSize(20).height(50).onClick(() {this.counter[0].incrCounter();this.counter[0].incrSubCounter(10);})Text(Parent: set.counter to 10).fontSize(20).height(50).onClick(() {this.counter[0].setSubCounter(10);})}}}
}ObjectLink图示如下 【反例】
如果用Prop替代ObjectLink。点击第一个click handlerUI刷新正常。但是点击第二个onClick事件Prop 对变量做了一个本地拷贝CounterComp的第一个Text并不会刷新。
this.value.subCounter和this.subValue并不是同一个对象。所以this.value.subCounter的改变并没有改变this.subValue的拷贝对象Text(this.subValue.counter: ${this.subValue.counter})不会刷新。
Component
struct CounterComp {Prop value: ParentCounter new ParentCounter(0);Prop subValue: SubCounter new SubCounter(0);build() {Column({ space: 10 }) {Text(this.subValue.counter: ${this.subValue.counter}).fontSize(20).onClick(() {// 1st click handlerthis.subValue.counter 7;})Text(this.value.counterincrease 7 ).fontSize(20).onClick(() {// 2nd click handlerthis.value.incrSubCounter(7);})Divider().height(2)}}
}Prop拷贝的关系图示如下 【正例】
可以通过从ParentComp到CounterComp仅拷贝一份Prop value: ParentCounter同时必须避免再多拷贝一份SubCounter。 在CounterComp组件中只使用一个Prop counterCounter。 添加另一个子组件SubCounterComp其中包含ObjectLink subCounter: SubCounter。此ObjectLink可确保观察到SubCounter对象属性更改并且UI更新正常。 ObjectLink subCounter: SubCounter与CounterComp中的Prop counterCounter的this.counter.subCounter共享相同的SubCounter对象。
let nextId 1;Observed
class SubCounter {counter: number;constructor(c: number) {this.counter c;}
}Observed
class ParentCounter {id: number;counter: number;subCounter: SubCounter;incrCounter() {this.counter;}incrSubCounter(c: number) {this.subCounter.counter c;}setSubCounter(c: number): void {this.subCounter.counter c;}constructor(c: number) {this.id nextId;this.counter c;this.subCounter new SubCounter(c);}
}Component
struct SubCounterComp {ObjectLink subValue: SubCounter;build() {Text(SubCounterComp: this.subValue.counter: ${this.subValue.counter}).onClick(() {// 2nd click handlerthis.subValue.counter 7;})}
}
Component
struct CounterComp {Prop value: ParentCounter;build() {Column({ space: 10 }) {Text(this.value.incrCounter(): this.value.counter: ${this.value.counter}).fontSize(20).onClick(() {// 1st click handlerthis.value.incrCounter();})SubCounterComp({ subValue: this.value.subCounter })Text(this.value.incrSubCounter()).onClick(() {// 3rd click handlerthis.value.incrSubCounter(77);})Divider().height(2)}}
}
Entry
Component
struct ParentComp {State counter: ParentCounter[] [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)];build() {Row() {Column() {CounterComp({ value: this.counter[0] })CounterComp({ value: this.counter[1] })CounterComp({ value: this.counter[2] })Divider().height(5)ForEach(this.counter,(item: ParentCounter) {CounterComp({ value: item })},(item: ParentCounter) item.id.toString())Divider().height(5)Text(Parent: reset entire counter).fontSize(20).height(50).onClick(() {this.counter [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)];})Text(Parent: incr counter[0].counter).fontSize(20).height(50).onClick(() {this.counter[0].incrCounter();this.counter[0].incrSubCounter(10);})Text(Parent: set.counter to 10).fontSize(20).height(50).onClick(() {this.counter[0].setSubCounter(10);})}}}
}拷贝关系图示如下 在Observed装饰类的构造函数中延时更改成员变量
在状态管理中使用Observed装饰类后会给该类使用一层“代理”进行包装。当在组件中改变该类的成员变量时会被该代理进行拦截在更改数据源中值的同时也会将变化通知给绑定的组件从而实现观测变化与触发刷新。当开发者在类的构造函数中对成员变量进行赋值或者修改时此修改不会经过代理因为是直接对数据源中的值进行修改也就无法被观测到。所以如果开发者在类的构造函数中使用定时器修改类中的成员变量即使该修改成功执行了也不会触发UI的刷新。
【反例】
Observed
class RenderClass {waitToRender: boolean false;constructor() {setTimeout(() {this.waitToRender true;console.log(change waitToRender to this.waitToRender);}, 1000)}
}Entry
Component
struct Index {State Watch(renderClassChange) renderClass: RenderClass new RenderClass();State textColor: Color Color.Black;renderClassChange() {console.log(Render Class Change waitToRender is this.renderClass.waitToRender);}build() {Row() {Column() {Text(Render Class waitToRender is this.renderClass.waitToRender).fontSize(20).fontColor(this.textColor)Button(Show).onClick(() {// 使用其他状态变量强行刷新UI的做法并不推荐此处仅用来检测waitToRender的值是否更新this.textColor Color.Red;})}.width(100%)}.height(100%)}
}上文的示例代码中在RenderClass的构造函数中使用定时器在1秒后修改了waitToRender的值但是不会触发UI的刷新。此时点击按钮强行刷新Text组件可以看到waitToRender的值已经被修改成了true。
【正例】
Observed
class RenderClass {waitToRender: boolean false;constructor() {}
}Entry
Component
struct Index {State Watch(renderClassChange) renderClass: RenderClass new RenderClass();renderClassChange() {console.log(Render Class Change waitToRender is this.renderClass.waitToRender);}onPageShow() {setTimeout(() {this.renderClass.waitToRender true;console.log(change waitToRender to this.renderClass.waitToRender);}, 1000)}build() {Row() {Column() {Text(Render Class Wait To Render is this.renderClass.waitToRender).fontSize(20)}.width(100%)}.height(100%)}
}上文的示例代码将定时器修改移入到组件内此时界面显示时会先显示“Render Class Change waitToRender is false”。待定时器触发时界面刷新显示“Render Class Change waitToRender is true”。
因此更推荐开发者在组件中对Observed装饰的类成员变量进行修改实现刷新。
在Observed装饰的类内使用static方法进行初始化
在Observed装饰的类内尽量避免使用static方法进行初始化在创建时会绕过Observed的实现导致无法被代理UI不刷新。
Entry
Component
struct MainPage {State viewModel: ViewModel ViewModel.build();build() {Column() {Button(Click).onClick((event) {this.viewModel.subViewModel.isShow !this.viewModel.subViewModel.isShow;})SubComponent({ viewModel: this.viewModel.subViewModel })}.padding({ top: 60 }).width(100%).alignItems(HorizontalAlign.Center)}
}Component
struct SubComponent {ObjectLink viewModel: SubViewModel;build() {Column() {if (this.viewModel.isShow) {Text(click to take effect);}}}
}class ViewModel {subViewModel: SubViewModel SubViewModel.build(); //内部静态方法创建static build() {console.log(ViewModel build())return new ViewModel();}
}Observed
class SubViewModel {isShow?: boolean false;static build() {//只有在SubViewModel内部的静态方法创建对象会影响关联console.log(SubViewModel build())let viewModel new SubViewModel();return viewModel;}
}上文的示例中在自定义组件ViewModel中使用static方法进行初始化此时点击Click按钮页面中并不会显示click to take effect。
因此不推荐开发者在自定义的类装饰器内使用static方法进行初始化。
为了能让大家更好的学习鸿蒙HarmonyOS NEXT开发技术这边特意整理了《鸿蒙开发学习手册》共计890页希望对大家有所帮助https://qr21.cn/FV7h05
《鸿蒙开发学习手册》
如何快速入门https://qr21.cn/FV7h05
基本概念构建第一个ArkTS应用…… 开发基础知识https://qr21.cn/FV7h05
应用基础知识配置文件应用数据管理应用安全管理应用隐私保护三方应用调用管控机制资源分类与访问学习ArkTS语言…… 基于ArkTS 开发https://qr21.cn/FV7h05
Ability开发UI开发公共事件与通知窗口管理媒体安全网络与链接电话服务数据管理后台任务(Background Task)管理设备管理设备使用信息统计DFX国际化开发折叠屏系列…… 鸿蒙开发面试真题含参考答案https://qr18.cn/F781PH 鸿蒙开发面试大盘集篇共计319页https://qr18.cn/F781PH
1.项目开发必备面试题 2.性能优化方向 3.架构方向 4.鸿蒙开发系统底层方向 5.鸿蒙音视频开发方向 6.鸿蒙车载开发方向 7.鸿蒙南向开发方向