• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 状态管理合理使用开发指导
2<!--Kit: ArkUI-->
3<!--Subsystem: ArkUI-->
4<!--Owner: @jiyujia926-->
5<!--Designer: @s10021109-->
6<!--Tester: @TerryTsao-->
7<!--Adviser: @zhang_yixin13-->
8
9由于对状态管理当前的特性并不了解,许多开发者在使用状态管理进行开发时会遇到UI不刷新、刷新性能差的情况。对此,本篇将从两个方向,对一共五个典型场景进行分析,同时提供相应的正例和反例,帮助开发者学习如何合理使用状态管理进行开发。
10
11## 合理使用属性
12
13### 将简单属性数组合并成对象数组
14
15在开发过程中,我们经常会需要设置多个组件的同一种属性,比如Text组件的内容、组件的宽度、高度等样式信息等。将这些属性保存在一个数组中,配合ForEach进行使用是一种简单且方便的方法。
16
17```typescript
18@Entry
19@Component
20struct Index {
21  @State items: string[] = [];
22  @State ids: string[] = [];
23  @State age: number[] = [];
24  @State gender: string[] = [];
25
26  aboutToAppear() {
27    this.items.push("Head");
28    this.items.push("List");
29    for (let i = 0; i < 20; i++) {
30      this.ids.push("id: " + Math.floor(Math.random() * 1000));
31      this.age.push(Math.floor(Math.random() * 100 % 40));
32      this.gender.push(Math.floor(Math.random() * 100) % 2 == 0 ? "Male" : "Female");
33    }
34  }
35
36  isRenderText(index: number) : number {
37    console.info(`index ${index} is rendered`);
38    return 1;
39  }
40
41  build() {
42    Row() {
43      Column() {
44        ForEach(this.items, (item: string) => {
45          if (item == "Head") {
46            Text("Personal Info")
47              .fontSize(40)
48          } else if (item == "List") {
49            List() {
50              ForEach(this.ids, (id: string, index) => {
51                ListItem() {
52                  Row() {
53                    Text(id)
54                      .fontSize(20)
55                      .margin({
56                        left: 30,
57                        right: 5
58                      })
59                    Text("age: " + this.age[index as number])
60                      .fontSize(20)
61                      .margin({
62                        left: 5,
63                        right: 5
64                      })
65                      .position({x: 100})
66                      .opacity(this.isRenderText(index))
67                      .onClick(() => {
68                        this.age[index]++;
69                      })
70                    Text("gender: " + this.gender[index as number])
71                      .margin({
72                        left: 5,
73                        right: 5
74                      })
75                      .position({x: 180})
76                      .fontSize(20)
77                  }
78                }
79                .margin({
80                  top: 5,
81                  bottom: 5
82                })
83              })
84            }
85          }
86        })
87      }
88    }
89  }
90}
91```
92
93上述代码运行效果如下。
94
95![properly-use-state-management-to-develope-1](figures/properly-use-state-management-to-develope-1.gif)
96
97页面内通过ForEach显示了20条信息,当点击某一条信息中age的Text组件时,可以通过日志发现其他的19条信息中age的Text组件也进行了刷新(这体现在日志上,所有的age的Text组件都打出了日志),但实际上其他19条信息的age的数值并没有改变,也就是说其他19个Text组件并不需要刷新。
98
99这是因为当前状态管理的一个特性。假设存在一个被[@State](./arkts-state.md)修饰的number类型的数组Num[],其中有20个元素,值分别为0到19。这20个元素分别绑定了一个Text组件,当改变其中一个元素,例如第0号元素的值从0改成1,除了0号元素绑定的Text组件会刷新之外,其他的19个Text组件也会刷新,即使1到19号元素的值并没有改变。
100
101这个特性普遍的出现在简单类型数组的场景中,当数组中的元素够多时,会对UI的刷新性能有很大的负面影响。这种“不需要刷新的组件被刷新”的现象即是“冗余刷新”,当“冗余刷新”的节点过多时,UI的刷新效率会大幅度降低,因此需要减少“冗余刷新”,也就是做到**精准控制组件的更新范围**。
102
103为了减少由简单的属性相关的数组引起的“冗余刷新”,需要将属性数组转变为对象数组,配合自定义组件,实现精准控制更新范围。下面为修改后的代码。
104
105```typescript
106@Observed
107class InfoList extends Array<Info> {
108};
109@Observed
110class Info {
111  ids: number;
112  age: number;
113  gender: string;
114
115  constructor() {
116    this.ids = Math.floor(Math.random() * 1000);
117    this.age = Math.floor(Math.random() * 100 % 40);
118    this.gender = Math.floor(Math.random() * 100) % 2 == 0 ? "Male" : "Female";
119  }
120}
121@Component
122struct Information {
123  @ObjectLink info: Info;
124  @State index: number = 0;
125  isRenderText(index: number) : number {
126    console.info(`index ${index} is rendered`);
127    return 1;
128  }
129
130  build() {
131    Row() {
132      Text("id: " + this.info.ids)
133        .fontSize(20)
134        .margin({
135          left: 30,
136          right: 5
137        })
138      Text("age: " + this.info.age)
139        .fontSize(20)
140        .margin({
141          left: 5,
142          right: 5
143        })
144        .position({x: 100})
145        .opacity(this.isRenderText(this.index))
146        .onClick(() => {
147          this.info.age++;
148        })
149      Text("gender: " + this.info.gender)
150        .margin({
151          left: 5,
152          right: 5
153        })
154        .position({x: 180})
155        .fontSize(20)
156    }
157  }
158}
159@Entry
160@Component
161struct Page {
162  @State infoList: InfoList = new InfoList();
163  @State items: string[] = [];
164  aboutToAppear() {
165    this.items.push("Head");
166    this.items.push("List");
167    for (let i = 0; i < 20; i++) {
168      this.infoList.push(new Info());
169    }
170  }
171
172  build() {
173    Row() {
174      Column() {
175        ForEach(this.items, (item: string) => {
176          if (item == "Head") {
177            Text("Personal Info")
178              .fontSize(40)
179          } else if (item == "List") {
180            List() {
181              ForEach(this.infoList, (info: Info, index) => {
182                ListItem() {
183                  Information({
184                    info: info,
185                    index: index
186                  })
187                }
188                .margin({
189                  top: 5,
190                  bottom: 5
191                })
192              })
193            }
194          }
195        })
196      }
197    }
198  }
199}
200```
201
202上述代码的运行效果如下。
203
204![properly-use-state-management-to-develope-2](figures/properly-use-state-management-to-develope-2.gif)
205
206修改后的代码使用对象数组代替了原有的多个属性数组,能够避免数组的“冗余刷新”的情况。这是因为对于数组来说,对象内的变化是无法感知的,数组只能观测数组项层级的变化,例如新增数据项,修改数据项(普通数组是直接修改数据项的值,在对象数组的场景下是整个对象被重新赋值,改变某个数据项对象中的属性不会被观测到)、删除数据项等。这意味着当改变对象内的某个属性时,对于数组来说,对象是没有变化的,也就不会去刷新。在当前状态管理的观测能力中,除了数组嵌套对象的场景外,对象嵌套对象的场景也是无法观测到变化的,这一部分内容将在[将复杂对象拆分成多个小对象的集合](#将复杂大对象拆分成多个小对象的集合)中讲到。同时修改代码时使用了自定义组件与ForEach的结合,这一部分内容将在[在ForEach中使用自定义组件搭配对象数组](#在foreach中使用自定义组件搭配对象数组)讲到。
207
208### 将复杂大对象拆分成多个小对象的集合
209
210> **说明:**
211>
212> 从API version 11开始,推荐优先使用[@Track装饰器](arkts-track.md)解决该场景的问题。
213
214在开发过程中,我们有时会定义一个大的对象,其中包含了很多样式相关的属性,并且在父子组件间传递这个对象,将其中的属性绑定在组件上。
215
216```typescript
217@Observed
218class UiStyle {
219  translateX: number = 0;
220  translateY: number = 0;
221  scaleX: number = 0.3;
222  scaleY: number = 0.3;
223  width: number = 336;
224  height: number = 178;
225  posX: number = 10;
226  posY: number = 50;
227  alpha: number = 0.5;
228  borderRadius: number = 24;
229  imageWidth: number = 78;
230  imageHeight: number = 78;
231  translateImageX: number = 0;
232  translateImageY: number = 0;
233  fontSize: number = 20;
234}
235@Component
236struct SpecialImage {
237  @ObjectLink uiStyle: UiStyle;
238  private isRenderSpecialImage() : number { // 显示组件是否渲染的函数
239    console.info("SpecialImage is rendered");
240    return 1;
241  }
242  build() {
243    Image($r('app.media.icon')) // 此处'app.media.icon'仅作示例,请开发者自行替换,否则imageSource创建失败会导致后续无法正常执行。
244      .width(this.uiStyle.imageWidth)
245      .height(this.uiStyle.imageHeight)
246      .margin({ top: 20 })
247      .translate({
248        x: this.uiStyle.translateImageX,
249        y: this.uiStyle.translateImageY
250      })
251      .opacity(this.isRenderSpecialImage()) // 如果Image重新渲染,该函数将被调用
252  }
253}
254@Component
255struct PageChild {
256  @ObjectLink uiStyle: UiStyle
257  // 下面的函数用于显示组件是否被渲染
258  private isRenderColumn() : number {
259    console.info("Column is rendered");
260    return 1;
261  }
262  private isRenderStack() : number {
263    console.info("Stack is rendered");
264    return 1;
265  }
266  private isRenderImage() : number {
267    console.info("Image is rendered");
268    return 1;
269  }
270  private isRenderText() : number {
271    console.info("Text is rendered");
272    return 1;
273  }
274
275  build() {
276    Column() {
277      SpecialImage({
278        uiStyle: this.uiStyle
279      })
280      Stack() {
281        Column() {
282          Image($r('app.media.icon')) // 此处'app.media.icon'仅作示例,请开发者自行替换,否则imageSource创建失败会导致后续无法正常执行。
283            .opacity(this.uiStyle.alpha)
284            .scale({
285              x: this.uiStyle.scaleX,
286              y: this.uiStyle.scaleY
287            })
288            .padding(this.isRenderImage())
289            .width(300)
290            .height(300)
291        }
292        .width('100%')
293        .position({ y: -80 })
294        Stack() {
295          Text("Hello World")
296            .fontColor("#182431")
297            .fontWeight(FontWeight.Medium)
298            .fontSize(this.uiStyle.fontSize)
299            .opacity(this.isRenderText())
300            .margin({ top: 12 })
301        }
302        .opacity(this.isRenderStack())
303        .position({
304          x: this.uiStyle.posX,
305          y: this.uiStyle.posY
306        })
307        .width('100%')
308        .height('100%')
309      }
310      .margin({ top: 50 })
311      .borderRadius(this.uiStyle.borderRadius)
312      .opacity(this.isRenderStack())
313      .backgroundColor("#FFFFFF")
314      .width(this.uiStyle.width)
315      .height(this.uiStyle.height)
316      .translate({
317        x: this.uiStyle.translateX,
318        y: this.uiStyle.translateY
319      })
320      Column() {
321        Button("Move")
322          .width(312)
323          .fontSize(20)
324          .backgroundColor("#FF007DFF")
325          .margin({ bottom: 10 })
326          .onClick(() => {
327            this.getUIContext().animateTo({
328              duration: 500
329            },() => {
330              this.uiStyle.translateY = (this.uiStyle.translateY + 180) % 250;
331            })
332          })
333        Button("Scale")
334          .borderRadius(20)
335          .backgroundColor("#FF007DFF")
336          .fontSize(20)
337          .width(312)
338          .onClick(() => {
339            this.uiStyle.scaleX = (this.uiStyle.scaleX + 0.6) % 0.8;
340          })
341      }
342      .position({
343        y:666
344      })
345      .height('100%')
346      .width('100%')
347
348    }
349    .opacity(this.isRenderColumn())
350    .width('100%')
351    .height('100%')
352
353  }
354}
355@Entry
356@Component
357struct Page {
358  @State uiStyle: UiStyle = new UiStyle();
359  build() {
360    Stack() {
361      PageChild({
362        uiStyle: this.uiStyle
363      })
364    }
365    .backgroundColor("#F1F3F5")
366  }
367}
368```
369
370上述代码的运行效果如下。
371
372![properly-use-state-management-to-develope-3](figures/properly-use-state-management-to-develope-3.gif)
373
374优化前点击move按钮的脏节点更新耗时如下图:
375
376![img](figures/properly-use-state-management-to-develope-11.PNG)
377
378在上面的示例中,UiStyle定义了多个属性,并且这些属性分别被多个组件关联。当点击任意一个按钮更改其中的某些属性时,会导致所有这些关联uiStyle的组件进行刷新,虽然它们其实并不需要进行刷新(因为组件的属性都没有改变)。通过定义的一系列isRender函数,可以观察到这些组件的刷新。当点击“move”按钮进行平移动画时,由于translateY的值的多次改变,会导致每一次都存在“冗余刷新”的问题,这对应用的性能有着很大的负面影响。
379
380这是因为当前状态管理的一个刷新机制,假设定义了一个有20个属性的类,创建类的对象实例,将20个属性绑定到组件上,这时修改其中的某个属性,除了这个属性关联的组件会刷新之外,其他的19个属性关联的组件也都会刷新,即使这些属性本身并没有发生变化。
381
382这个机制会导致在使用一个复杂大对象与多个组件关联时,刷新性能的下降。对此,推荐将一个复杂大对象拆分成多个小对象的集合,在保留原有代码结构的基础上,减少“冗余刷新”,实现精准控制组件的更新范围。
383
384```typescript
385@Observed
386class NeedRenderImage { // 在同一组件中使用的属性可以划分为相同的类
387  public translateImageX: number = 0;
388  public translateImageY: number = 0;
389  public imageWidth:number = 78;
390  public imageHeight:number = 78;
391}
392@Observed
393class NeedRenderScale { // 在一起使用的属性可以划分为相同的类
394  public scaleX: number = 0.3;
395  public scaleY: number = 0.3;
396}
397@Observed
398class NeedRenderAlpha { // 在不同地方使用的属性可以划分为相同的类
399  public alpha: number = 0.5;
400}
401@Observed
402class NeedRenderSize { // 在一起使用的属性可以划分为相同的类
403  public width: number = 336;
404  public height: number = 178;
405}
406@Observed
407class NeedRenderPos { // 在一起使用的属性可以划分为相同的类
408  public posX: number = 10;
409  public posY: number = 50;
410}
411@Observed
412class NeedRenderBorderRadius { // 在不同地方使用的属性可以划分为相同的类
413  public borderRadius: number = 24;
414}
415@Observed
416class NeedRenderFontSize { // 在不同地方使用的属性可以划分为相同的类
417  public fontSize: number = 20;
418}
419@Observed
420class NeedRenderTranslate { // 在一起使用的属性可以划分为相同的类
421  public translateX: number = 0;
422  public translateY: number = 0;
423}
424@Observed
425class UiStyle {
426  // 使用NeedRenderxxx类
427  needRenderTranslate: NeedRenderTranslate = new NeedRenderTranslate();
428  needRenderFontSize: NeedRenderFontSize = new NeedRenderFontSize();
429  needRenderBorderRadius: NeedRenderBorderRadius = new NeedRenderBorderRadius();
430  needRenderPos: NeedRenderPos = new NeedRenderPos();
431  needRenderSize: NeedRenderSize = new NeedRenderSize();
432  needRenderAlpha: NeedRenderAlpha = new NeedRenderAlpha();
433  needRenderScale: NeedRenderScale = new NeedRenderScale();
434  needRenderImage: NeedRenderImage = new NeedRenderImage();
435}
436@Component
437struct SpecialImage {
438  @ObjectLink uiStyle : UiStyle;
439  @ObjectLink needRenderImage: NeedRenderImage // 从其父组件接收新类
440  private isRenderSpecialImage() : number { // 显示组件是否渲染的函数
441    console.info("SpecialImage is rendered");
442    return 1;
443  }
444  build() {
445    Image($r('app.media.background')) // 此处'app.media.icon'仅作示例,请开发者自行替换,否则imageSource创建失败会导致后续无法正常执行。
446      .width(this.needRenderImage.imageWidth) // 使用this.needRenderImage.xxx
447      .height(this.needRenderImage.imageHeight)
448      .margin({top:20})
449      .translate({
450        x: this.needRenderImage.translateImageX,
451        y: this.needRenderImage.translateImageY
452      })
453      .opacity(this.isRenderSpecialImage()) // 如果Image重新渲染,该函数将被调用
454  }
455}
456@Component
457struct PageChild {
458  @ObjectLink uiStyle: UiStyle;
459  @ObjectLink needRenderTranslate: NeedRenderTranslate; // 从其父组件接收新定义的NeedRenderxxx类的实例
460  @ObjectLink needRenderFontSize: NeedRenderFontSize;
461  @ObjectLink needRenderBorderRadius: NeedRenderBorderRadius;
462  @ObjectLink needRenderPos: NeedRenderPos;
463  @ObjectLink needRenderSize: NeedRenderSize;
464  @ObjectLink needRenderAlpha: NeedRenderAlpha;
465  @ObjectLink needRenderScale: NeedRenderScale;
466  // 下面的函数用于显示组件是否被渲染
467  private isRenderColumn() : number {
468    console.info("Column is rendered");
469    return 1;
470  }
471  private isRenderStack() : number {
472    console.info("Stack is rendered");
473    return 1;
474  }
475  private isRenderImage() : number {
476    console.info("Image is rendered");
477    return 1;
478  }
479  private isRenderText() : number {
480    console.info("Text is rendered");
481    return 1;
482  }
483
484  build() {
485    Column() {
486      SpecialImage({
487        uiStyle: this.uiStyle,
488        needRenderImage: this.uiStyle.needRenderImage // 传递给子组件
489      })
490      Stack() {
491        Column() {
492          Image($r('app.media.background')) // 此处'app.media.icon'仅作示例,请开发者自行替换,否则imageSource创建失败会导致后续无法正常执行。
493            .opacity(this.needRenderAlpha.alpha)
494            .scale({
495              x: this.needRenderScale.scaleX, // 使用this.needRenderXxx.xxx
496              y: this.needRenderScale.scaleY
497            })
498            .padding(this.isRenderImage())
499            .width(300)
500            .height(300)
501        }
502        .width('100%')
503        .position({ y: -80 })
504
505        Stack() {
506          Text("Hello World")
507            .fontColor("#182431")
508            .fontWeight(FontWeight.Medium)
509            .fontSize(this.needRenderFontSize.fontSize)
510            .opacity(this.isRenderText())
511            .margin({ top: 12 })
512        }
513        .opacity(this.isRenderStack())
514        .position({
515          x: this.needRenderPos.posX,
516          y: this.needRenderPos.posY
517        })
518        .width('100%')
519        .height('100%')
520      }
521      .margin({ top: 50 })
522      .borderRadius(this.needRenderBorderRadius.borderRadius)
523      .opacity(this.isRenderStack())
524      .backgroundColor("#FFFFFF")
525      .width(this.needRenderSize.width)
526      .height(this.needRenderSize.height)
527      .translate({
528        x: this.needRenderTranslate.translateX,
529        y: this.needRenderTranslate.translateY
530      })
531
532      Column() {
533        Button("Move")
534          .width(312)
535          .fontSize(20)
536          .backgroundColor("#FF007DFF")
537          .margin({ bottom: 10 })
538          .onClick(() => {
539            this.getUIContext().animateTo({
540              duration: 500
541            }, () => {
542              this.needRenderTranslate.translateY = (this.needRenderTranslate.translateY + 180) % 250;
543            })
544          })
545        Button("Scale")
546          .borderRadius(20)
547          .backgroundColor("#FF007DFF")
548          .fontSize(20)
549          .width(312)
550          .margin({ bottom: 10 })
551          .onClick(() => {
552            this.needRenderScale.scaleX = (this.needRenderScale.scaleX + 0.6) % 0.8;
553          })
554        Button("Change Image")
555          .borderRadius(20)
556          .backgroundColor("#FF007DFF")
557          .fontSize(20)
558          .width(312)
559          .onClick(() => { // 在父组件中,仍使用 this.uiStyle.endRenderXxx.xxx 更改属性
560            this.uiStyle.needRenderImage.imageWidth = (this.uiStyle.needRenderImage.imageWidth + 30) % 160;
561            this.uiStyle.needRenderImage.imageHeight = (this.uiStyle.needRenderImage.imageHeight + 30) % 160;
562          })
563      }
564      .position({
565        y: 616
566      })
567      .height('100%')
568      .width('100%')
569    }
570    .opacity(this.isRenderColumn())
571    .width('100%')
572    .height('100%')
573  }
574}
575@Entry
576@Component
577struct Page {
578  @State uiStyle: UiStyle = new UiStyle();
579  build() {
580    Stack() {
581      PageChild({
582        uiStyle: this.uiStyle,
583        needRenderTranslate: this.uiStyle.needRenderTranslate, // 传递needRenderxxx类给子组件
584        needRenderFontSize: this.uiStyle.needRenderFontSize,
585        needRenderBorderRadius: this.uiStyle.needRenderBorderRadius,
586        needRenderPos: this.uiStyle.needRenderPos,
587        needRenderSize: this.uiStyle.needRenderSize,
588        needRenderAlpha: this.uiStyle.needRenderAlpha,
589        needRenderScale: this.uiStyle.needRenderScale
590      })
591    }
592    .backgroundColor("#F1F3F5")
593  }
594}
595```
596
597上述代码的运行效果如下。![properly-use-state-management-to-develope-4](figures/properly-use-state-management-to-develope-4.gif)
598
599优化后点击move按钮的脏节点更新耗时如下图:
600
601![img](figures/properly-use-state-management-to-develope-12.PNG)
602
603修改后的代码将原来的大类中的十五个属性拆成了八个小类,并且在绑定的组件上也做了相应的适配。属性拆分遵循以下几点原则:
604
605- 只作用在同一个组件上的多个属性可以被拆分进同一个新类,即示例中的NeedRenderImage。适用于组件经常被不关联的属性改变而引起刷新的场景,这个时候就要考虑拆分属性,或者重新考虑ViewModel设计是否合理。
606- 经常被同时使用的属性可以被拆分进同一个新类,即示例中的NeedRenderScale、NeedRenderTranslate、NeedRenderPos、NeedRenderSize。适用于属性经常成对出现,或者被作用在同一个样式上的情况,例如.translate、.position、.scale等(这些样式通常会接收一个对象作为参数)。
607- 可能被用在多个组件上或相对较独立的属性应该被单独拆分进一个新类,即示例中的NeedRenderAlpha,NeedRenderBorderRadius、NeedRenderFontSize。适用于一个属性作用在多个组件上或者与其他属性没有联系的情况,例如.opacity、.borderRadius等(这些样式通常相对独立)。
608
609属性拆分的原理和属性合并类似,都是在嵌套场景下,状态管理无法观测二层以上的属性变化,所以不会因为二层的数据变化导致一层关联的其他属性被刷新,同时利用[@Observed](./arkts-observed-and-objectlink.md)和[@ObjectLink](./arkts-observed-and-objectlink.md)在父子节点间传递二层的对象,从而在子组件中正常的观测二层的数据变化,实现精准刷新。<!--Del-->关于属性拆分的详细内容,可以查看[精准控制组件的更新范围](../../performance/precisely-control-render-scope.md)。<!--DelEnd-->
610
611使用[@Track](./arkts-track.md)装饰器则无需做属性拆分,也能达到同样控制组件更新范围的作用。
612
613```ts
614@Observed
615class UiStyle {
616  @Track translateX: number = 0;
617  @Track translateY: number = 0;
618  @Track scaleX: number = 0.3;
619  @Track scaleY: number = 0.3;
620  @Track width: number = 336;
621  @Track height: number = 178;
622  @Track posX: number = 10;
623  @Track posY: number = 50;
624  @Track alpha: number = 0.5;
625  @Track borderRadius: number = 24;
626  @Track imageWidth: number = 78;
627  @Track imageHeight: number = 78;
628  @Track translateImageX: number = 0;
629  @Track translateImageY: number = 0;
630  @Track fontSize: number = 20;
631}
632@Component
633struct SpecialImage {
634  @ObjectLink uiStyle: UiStyle;
635  private isRenderSpecialImage() : number { // 显示组件是否渲染的函数
636    console.info("SpecialImage is rendered");
637    return 1;
638  }
639  build() {
640    Image($r('app.media.foreground')) // 此处'app.media.icon'仅作示例,请开发者自行替换,否则imageSource创建失败会导致后续无法正常执行。
641      .width(this.uiStyle.imageWidth)
642      .height(this.uiStyle.imageHeight)
643      .margin({ top: 20 })
644      .translate({
645        x: this.uiStyle.translateImageX,
646        y: this.uiStyle.translateImageY
647      })
648      .opacity(this.isRenderSpecialImage()) // 如果Image重新渲染,该函数将被调用
649  }
650}
651@Component
652struct PageChild {
653  @ObjectLink uiStyle: UiStyle
654  // 下面的函数用于显示组件是否被渲染
655  private isRenderColumn() : number {
656    console.info("Column is rendered");
657    return 1;
658  }
659  private isRenderStack() : number {
660    console.info("Stack is rendered");
661    return 1;
662  }
663  private isRenderImage() : number {
664    console.info("Image is rendered");
665    return 1;
666  }
667  private isRenderText() : number {
668    console.info("Text is rendered");
669    return 1;
670  }
671
672  build() {
673    Column() {
674      SpecialImage({
675        uiStyle: this.uiStyle
676      })
677      Stack() {
678        Column() {
679          Image($r('app.media.foreground')) // 此处'app.media.icon'仅作示例,请开发者自行替换,否则imageSource创建失败会导致后续无法正常执行。
680            .opacity(this.uiStyle.alpha)
681            .scale({
682              x: this.uiStyle.scaleX,
683              y: this.uiStyle.scaleY
684            })
685            .padding(this.isRenderImage())
686            .width(300)
687            .height(300)
688        }
689        .width('100%')
690        .position({ y: -80 })
691        Stack() {
692          Text("Hello World")
693            .fontColor("#182431")
694            .fontWeight(FontWeight.Medium)
695            .fontSize(this.uiStyle.fontSize)
696            .opacity(this.isRenderText())
697            .margin({ top: 12 })
698        }
699        .opacity(this.isRenderStack())
700        .position({
701          x: this.uiStyle.posX,
702          y: this.uiStyle.posY
703        })
704        .width('100%')
705        .height('100%')
706      }
707      .margin({ top: 50 })
708      .borderRadius(this.uiStyle.borderRadius)
709      .opacity(this.isRenderStack())
710      .backgroundColor("#FFFFFF")
711      .width(this.uiStyle.width)
712      .height(this.uiStyle.height)
713      .translate({
714        x: this.uiStyle.translateX,
715        y: this.uiStyle.translateY
716      })
717      Column() {
718        Button("Move")
719          .width(312)
720          .fontSize(20)
721          .backgroundColor("#FF007DFF")
722          .margin({ bottom: 10 })
723          .onClick(() => {
724            this.getUIContext().animateTo({
725              duration: 500
726            },() => {
727              this.uiStyle.translateY = (this.uiStyle.translateY + 180) % 250;
728            })
729          })
730        Button("Scale")
731          .borderRadius(20)
732          .backgroundColor("#FF007DFF")
733          .fontSize(20)
734          .width(312)
735          .onClick(() => {
736            this.uiStyle.scaleX = (this.uiStyle.scaleX + 0.6) % 0.8;
737          })
738      }
739      .position({
740        y:666
741      })
742      .height('100%')
743      .width('100%')
744
745    }
746    .opacity(this.isRenderColumn())
747    .width('100%')
748    .height('100%')
749
750  }
751}
752@Entry
753@Component
754struct Page {
755  @State uiStyle: UiStyle = new UiStyle();
756  build() {
757    Stack() {
758      PageChild({
759        uiStyle: this.uiStyle
760      })
761    }
762    .backgroundColor("#F1F3F5")
763  }
764}
765```
766
767
768
769### 使用@Observed装饰或被声明为状态变量的类对象绑定组件
770
771在开发过程中,会有“重置数据”的场景,将一个新创建的对象赋值给原有的状态变量,实现数据的刷新。如果不注意新创建对象的类型,可能会出现UI不刷新的现象。
772
773```typescript
774@Observed
775class Child {
776  count: number;
777  constructor(count: number) {
778    this.count = count
779  }
780}
781@Observed
782class ChildList extends Array<Child> {
783};
784@Observed
785class Ancestor {
786  childList: ChildList;
787  constructor(childList: ChildList) {
788    this.childList = childList;
789  }
790  public loadData() {
791    let tempList = [new Child(1), new Child(2), new Child(3), new Child(4), new Child(5)];
792    this.childList = tempList;
793  }
794
795  public clearData() {
796    this.childList = []
797  }
798}
799@Component
800struct CompChild {
801  @Link childList: ChildList;
802  @ObjectLink child: Child;
803
804  build() {
805    Row() {
806      Text(this.child.count+'')
807        .height(70)
808        .fontSize(20)
809        .borderRadius({
810          topLeft: 6,
811          topRight: 6
812        })
813        .margin({left: 50})
814      Button('X')
815        .backgroundColor(Color.Red)
816        .onClick(()=>{
817          let index = this.childList.findIndex((item) => {
818            return item.count === this.child.count
819          })
820          if (index !== -1) {
821            this.childList.splice(index, 1);
822          }
823        })
824        .margin({
825          left: 200,
826          right:30
827        })
828    }
829    .margin({
830      top:15,
831      left: 15,
832      right:10,
833      bottom:15
834    })
835    .borderRadius(6)
836    .backgroundColor(Color.Grey)
837  }
838}
839@Component
840struct CompList {
841  @ObjectLink@Watch('changeChildList') childList: ChildList;
842
843  changeChildList() {
844    console.info('CompList ChildList change');
845  }
846
847  isRenderCompChild(index: number) : number {
848    console.info("Comp Child is render" + index);
849    return 1;
850  }
851
852  build() {
853    Column() {
854      List() {
855        ForEach(this.childList, (item: Child, index) => {
856          ListItem() {
857            CompChild({
858              childList: this.childList,
859              child: item
860            })
861              .opacity(this.isRenderCompChild(index))
862          }
863
864        })
865      }
866      .height('70%')
867    }
868  }
869}
870@Component
871struct CompAncestor {
872  @ObjectLink ancestor: Ancestor;
873
874  build() {
875    Column() {
876      CompList({ childList: this.ancestor.childList })
877      Row() {
878        Button("Clear")
879          .onClick(() => {
880            this.ancestor.clearData()
881          })
882          .width(100)
883          .margin({right: 50})
884        Button("Recover")
885          .onClick(() => {
886            this.ancestor.loadData()
887          })
888          .width(100)
889      }
890    }
891  }
892}
893@Entry
894@Component
895struct Page {
896  @State childList: ChildList = [new Child(1), new Child(2), new Child(3), new Child(4),new Child(5)];
897  @State ancestor: Ancestor = new Ancestor(this.childList)
898
899  build() {
900    Column() {
901      CompAncestor({ ancestor: this.ancestor})
902    }
903  }
904}
905```
906
907上述代码运行效果如下。
908
909![properly-use-state-management-to-develope-5](figures/properly-use-state-management-to-develope-5.gif)
910
911上述代码维护了一个ChildList类型的数据源,点击"X"按钮删除一些数据后再点击Recover进行恢复ChildList,发现再次点击"X"按钮进行删除时,UI并没有刷新,同时也没有打印出“CompList ChildList change”的日志。
912
913代码中对数据源childList重新赋值时,是通过Ancestor对象的方法loadData。
914
915```typescript
916  public loadData() {
917    let tempList = [new Child(1), new Child(2), new Child(3), new Child(4), new Child(5)];
918    this.childList = tempList;
919  }
920```
921
922在loadData方法中,创建了一个临时的Child类型的数组tempList,并且将Ancestor对象的成员变量的childList指向了tempList。但是这里创建的Child[]类型的数组tempList其实并没有能被观测的能力(也就说它的变化无法主动触发UI刷新)。当它被赋值给childList之后,触发了ForEach的刷新,使得界面完成了重建,但是再次点击删除时,由于此时的childList已经指向了新的tempList代表的数组,并且这个数组并没有被观测的能力,是个静态的量,所以它的更改不会被观测到,也就不会引起UI的刷新。实际上这个时候childList里的数据已经减少了,只是UI没有刷新。
923
924有些开发者会注意到,在Page中初始化定义childList的时候,也是以这样一种方法去进行初始化的。
925
926```typescript
927@State childList: ChildList = [new Child(1), new Child(2), new Child(3), new Child(4),new Child(5)];
928@State ancestor: Ancestor = new Ancestor(this.childList)
929```
930
931但是由于这里的childList实际上是被@State装饰了,根据当前状态管理的观测能力,尽管右边赋值的是一个Child[]类型的数据,它并没有被@Observed装饰,这里的childList却依然具备了被观测的能力,所以能够正常的触发UI的刷新。当去掉childList的@State的装饰器后,不去重置数据源,也无法通过点击“X”按钮触发刷新。
932
933因此,需要将具有观测能力的类对象绑定组件,来确保当改变这些类对象的内容时,UI能够正常的刷新。
934
935```typescript
936@Observed
937class Child {
938  count: number;
939  constructor(count: number) {
940    this.count = count
941  }
942}
943@Observed
944class ChildList extends Array<Child> {
945};
946@Observed
947class Ancestor {
948  childList: ChildList;
949  constructor(childList: ChildList) {
950    this.childList = childList;
951  }
952  public loadData() {
953    let tempList = new ChildList();
954    for (let i = 1; i < 6; i ++) {
955      tempList.push(new Child(i));
956    }
957    this.childList = tempList;
958  }
959
960  public clearData() {
961    this.childList = []
962  }
963}
964@Component
965struct CompChild {
966  @Link childList: ChildList;
967  @ObjectLink child: Child;
968
969  build() {
970    Row() {
971      Text(this.child.count+'')
972        .height(70)
973        .fontSize(20)
974        .borderRadius({
975          topLeft: 6,
976          topRight: 6
977        })
978        .margin({left: 50})
979      Button('X')
980        .backgroundColor(Color.Red)
981        .onClick(()=>{
982          let index = this.childList.findIndex((item) => {
983            return item.count === this.child.count
984          })
985          if (index !== -1) {
986            this.childList.splice(index, 1);
987          }
988        })
989        .margin({
990          left: 200,
991          right:30
992        })
993    }
994    .margin({
995      top:15,
996      left: 15,
997      right:10,
998      bottom:15
999    })
1000    .borderRadius(6)
1001    .backgroundColor(Color.Grey)
1002  }
1003}
1004@Component
1005struct CompList {
1006  @ObjectLink@Watch('changeChildList') childList: ChildList;
1007
1008  changeChildList() {
1009    console.info('CompList ChildList change');
1010  }
1011
1012  isRenderCompChild(index: number) : number {
1013    console.info("Comp Child is render" + index);
1014    return 1;
1015  }
1016
1017  build() {
1018    Column() {
1019      List() {
1020        ForEach(this.childList, (item: Child, index) => {
1021          ListItem() {
1022            CompChild({
1023              childList: this.childList,
1024              child: item
1025            })
1026              .opacity(this.isRenderCompChild(index))
1027          }
1028
1029        })
1030      }
1031      .height('70%')
1032    }
1033  }
1034}
1035@Component
1036struct CompAncestor {
1037  @ObjectLink ancestor: Ancestor;
1038
1039  build() {
1040    Column() {
1041      CompList({ childList: this.ancestor.childList })
1042      Row() {
1043        Button("Clear")
1044          .onClick(() => {
1045            this.ancestor.clearData()
1046          })
1047          .width(100)
1048          .margin({right: 50})
1049        Button("Recover")
1050          .onClick(() => {
1051            this.ancestor.loadData()
1052          })
1053          .width(100)
1054      }
1055    }
1056  }
1057}
1058@Entry
1059@Component
1060struct Page {
1061  @State childList: ChildList = [new Child(1), new Child(2), new Child(3), new Child(4),new Child(5)];
1062  @State ancestor: Ancestor = new Ancestor(this.childList)
1063
1064  build() {
1065    Column() {
1066      CompAncestor({ ancestor: this.ancestor})
1067    }
1068  }
1069}
1070```
1071
1072上述代码运行效果如下。
1073
1074![properly-use-state-management-to-develope-6](figures/properly-use-state-management-to-develope-6.gif)
1075
1076核心的修改点是将原本Child[]类型的tempList修改为具有被观测能力的ChildList类。
1077
1078```typescript
1079public loadData() {
1080    let tempList = new ChildList();
1081    for (let i = 1; i < 6; i ++) {
1082      tempList.push(new Child(i));
1083    }
1084    this.childList = tempList;
1085  }
1086```
1087
1088ChildList类型在定义的时候使用了@Observed进行装饰,所以用new创建的对象tempList具有被观测的能力,因此在点击“X”按钮删除其中一条内容时,变量childList就能够观测到变化,所以触发了ForEach的刷新,最终UI渲染刷新。
1089
1090## 合理使用ForEach/LazyForEach
1091
1092### 减少使用LazyForEach的重建机制刷新UI
1093
1094开发过程中通常会将[LazyForEach](arkts-rendering-control-lazyforeach.md)和状态变量结合起来使用。
1095
1096```typescript
1097class BasicDataSource implements IDataSource {
1098  private listeners: DataChangeListener[] = [];
1099  private originDataArray: StringData[] = [];
1100
1101  public totalCount(): number {
1102    return 0;
1103  }
1104
1105  public getData(index: number): StringData {
1106    return this.originDataArray[index];
1107  }
1108
1109  registerDataChangeListener(listener: DataChangeListener): void {
1110    if (this.listeners.indexOf(listener) < 0) {
1111      console.info('add listener');
1112      this.listeners.push(listener);
1113    }
1114  }
1115
1116  unregisterDataChangeListener(listener: DataChangeListener): void {
1117    const pos = this.listeners.indexOf(listener);
1118    if (pos >= 0) {
1119      console.info('remove listener');
1120      this.listeners.splice(pos, 1);
1121    }
1122  }
1123
1124  notifyDataReload(): void {
1125    this.listeners.forEach(listener => {
1126      listener.onDataReloaded();
1127    })
1128  }
1129
1130  notifyDataAdd(index: number): void {
1131    this.listeners.forEach(listener => {
1132      listener.onDataAdd(index);
1133    })
1134  }
1135
1136  notifyDataChange(index: number): void {
1137    this.listeners.forEach(listener => {
1138      listener.onDataChange(index);
1139    })
1140  }
1141
1142  notifyDataDelete(index: number): void {
1143    this.listeners.forEach(listener => {
1144      listener.onDataDelete(index);
1145    })
1146  }
1147
1148  notifyDataMove(from: number, to: number): void {
1149    this.listeners.forEach(listener => {
1150      listener.onDataMove(from, to);
1151    })
1152  }
1153}
1154
1155class MyDataSource extends BasicDataSource {
1156  private dataArray: StringData[] = [];
1157
1158  public totalCount(): number {
1159    return this.dataArray.length;
1160  }
1161
1162  public getData(index: number): StringData {
1163    return this.dataArray[index];
1164  }
1165
1166  public addData(index: number, data: StringData): void {
1167    this.dataArray.splice(index, 0, data);
1168    this.notifyDataAdd(index);
1169  }
1170
1171  public pushData(data: StringData): void {
1172    this.dataArray.push(data);
1173    this.notifyDataAdd(this.dataArray.length - 1);
1174  }
1175
1176  public reloadData(): void {
1177    this.notifyDataReload();
1178  }
1179}
1180
1181class StringData {
1182  message: string;
1183  imgSrc: Resource;
1184  constructor(message: string, imgSrc: Resource) {
1185    this.message = message;
1186    this.imgSrc = imgSrc;
1187  }
1188}
1189
1190@Entry
1191@Component
1192struct MyComponent {
1193  private data: MyDataSource = new MyDataSource();
1194
1195  aboutToAppear() {
1196    for (let i = 0; i <= 9; i++) {
1197      // 此处'app.media.icon'仅作示例,请开发者自行替换,否则imageSource创建失败会导致后续无法正常执行。
1198      this.data.pushData(new StringData(`Click to add ${i}`, $r('app.media.icon')));
1199    }
1200  }
1201
1202  build() {
1203    List({ space: 3 }) {
1204      LazyForEach(this.data, (item: StringData, index: number) => {
1205        ListItem() {
1206          Column() {
1207            Text(item.message).fontSize(20)
1208              .onAppear(() => {
1209                console.info("text appear:" + item.message);
1210              })
1211            Image(item.imgSrc)
1212              .width(100)
1213              .height(100)
1214              .onAppear(() => {
1215                console.info("image appear");
1216              })
1217          }.margin({ left: 10, right: 10 })
1218        }
1219        .onClick(() => {
1220          item.message += '0';
1221          this.data.reloadData();
1222        })
1223      }, (item: StringData, index: number) => JSON.stringify(item))
1224    }.cachedCount(5)
1225  }
1226}
1227```
1228
1229上述代码运行效果如下。
1230
1231![properly-use-state-management-to-develope-7](figures/properly-use-state-management-to-develope-7.gif)
1232
1233可以观察到在点击更改message之后,图片“闪烁”了一下,同时输出了组件的onAppear日志,这说明组件进行了重建。这是因为在更改message之后,导致LazyForEach中这一项的key值发生了变化,使得LazyForEach在reloadData的时候将这一项ListItem进行了重建。Text组件仅仅更改显示的内容却发生了重建,而不是更新。而尽管Image组件没有需要重新绘制的内容,但是因为触发LazyForEach的重建,会使得同样位于ListItem下的Image组件重新创建。
1234
1235当前LazyForEach与状态变量都能触发UI的刷新,两者的性能开销是不一样的。使用LazyForEach刷新会对组件进行重建,如果包含了多个组件,则会产生比较大的性能开销。使用状态变量刷新会对组件进行刷新,具体到状态变量关联的组件上,相对于LazyForEach的重建来说,范围更小更精确。因此,推荐使用状态变量来触发LazyForEach中的组件刷新,这就需要使用自定义组件。
1236
1237```typescript
1238class BasicDataSource implements IDataSource {
1239  private listeners: DataChangeListener[] = [];
1240  private originDataArray: StringData[] = [];
1241
1242  public totalCount(): number {
1243    return 0;
1244  }
1245
1246  public getData(index: number): StringData {
1247    return this.originDataArray[index];
1248  }
1249
1250  registerDataChangeListener(listener: DataChangeListener): void {
1251    if (this.listeners.indexOf(listener) < 0) {
1252      console.info('add listener');
1253      this.listeners.push(listener);
1254    }
1255  }
1256
1257  unregisterDataChangeListener(listener: DataChangeListener): void {
1258    const pos = this.listeners.indexOf(listener);
1259    if (pos >= 0) {
1260      console.info('remove listener');
1261      this.listeners.splice(pos, 1);
1262    }
1263  }
1264
1265  notifyDataReload(): void {
1266    this.listeners.forEach(listener => {
1267      listener.onDataReloaded();
1268    })
1269  }
1270
1271  notifyDataAdd(index: number): void {
1272    this.listeners.forEach(listener => {
1273      listener.onDataAdd(index);
1274    })
1275  }
1276
1277  notifyDataChange(index: number): void {
1278    this.listeners.forEach(listener => {
1279      listener.onDataChange(index);
1280    })
1281  }
1282
1283  notifyDataDelete(index: number): void {
1284    this.listeners.forEach(listener => {
1285      listener.onDataDelete(index);
1286    })
1287  }
1288
1289  notifyDataMove(from: number, to: number): void {
1290    this.listeners.forEach(listener => {
1291      listener.onDataMove(from, to);
1292    })
1293  }
1294}
1295
1296class MyDataSource extends BasicDataSource {
1297  private dataArray: StringData[] = [];
1298
1299  public totalCount(): number {
1300    return this.dataArray.length;
1301  }
1302
1303  public getData(index: number): StringData {
1304    return this.dataArray[index];
1305  }
1306
1307  public addData(index: number, data: StringData): void {
1308    this.dataArray.splice(index, 0, data);
1309    this.notifyDataAdd(index);
1310  }
1311
1312  public pushData(data: StringData): void {
1313    this.dataArray.push(data);
1314    this.notifyDataAdd(this.dataArray.length - 1);
1315  }
1316}
1317
1318@Observed
1319class StringData {
1320  @Track message: string;
1321  @Track imgSrc: Resource;
1322  constructor(message: string, imgSrc: Resource) {
1323    this.message = message;
1324    this.imgSrc = imgSrc;
1325  }
1326}
1327
1328@Entry
1329@Component
1330struct MyComponent {
1331  @State data: MyDataSource = new MyDataSource();
1332
1333  aboutToAppear() {
1334    for (let i = 0; i <= 9; i++) {
1335      // 此处'app.media.icon'仅作示例,请开发者自行替换,否则imageSource创建失败会导致后续无法正常执行。
1336      this.data.pushData(new StringData(`Click to add ${i}`, $r('app.media.icon')));
1337    }
1338  }
1339
1340  build() {
1341    List({ space: 3 }) {
1342      LazyForEach(this.data, (item: StringData, index: number) => {
1343        ListItem() {
1344          ChildComponent({data: item})
1345        }
1346        .onClick(() => {
1347          item.message += '0';
1348        })
1349      }, (item: StringData, index: number) => index.toString())
1350    }.cachedCount(5)
1351  }
1352}
1353
1354@Component
1355struct ChildComponent {
1356  @ObjectLink data: StringData
1357  build() {
1358    Column() {
1359      Text(this.data.message).fontSize(20)
1360        .onAppear(() => {
1361          console.info("text appear:" + this.data.message);
1362        })
1363      Image(this.data.imgSrc)
1364        .width(100)
1365        .height(100)
1366    }.margin({ left: 10, right: 10 })
1367  }
1368}
1369```
1370
1371上述代码运行效果如下。
1372
1373![properly-use-state-management-to-develope-8](figures/properly-use-state-management-to-develope-8.gif)
1374
1375可以观察到UI能够正常刷新,图片没有“闪烁”,且没有输出日志信息,说明没有对Text组件和Image组件进行重建。
1376
1377这是因为使用自定义组件之后,可以通过@Observed和@ObjectLink配合去直接更改自定义组件内的状态变量实现刷新,而不需要利用LazyForEach进行重建。使用[@Track装饰器](arkts-track.md)分别装饰StringData类型中的message和imgSrc属性可以使更新范围进一步缩小到指定的Text组件。
1378
1379### 在ForEach中使用自定义组件搭配对象数组
1380
1381开发过程中经常会使用对象数组和[ForEach](arkts-rendering-control-foreach.md)结合起来使用,但是写法不当的话会出现UI不刷新的情况。
1382
1383```typescript
1384@Observed
1385class StyleList extends Array<TextStyles> {
1386};
1387@Observed
1388class TextStyles {
1389  fontSize: number;
1390
1391  constructor(fontSize: number) {
1392    this.fontSize = fontSize;
1393  }
1394}
1395@Entry
1396@Component
1397struct Page {
1398  @State styleList: StyleList = new StyleList();
1399  aboutToAppear() {
1400    for (let i = 15; i < 50; i++)
1401    this.styleList.push(new TextStyles(i));
1402  }
1403  build() {
1404    Column() {
1405      Text("Font Size List")
1406        .fontSize(50)
1407        .onClick(() => {
1408          for (let i = 0; i < this.styleList.length; i++) {
1409            this.styleList[i].fontSize++;
1410          }
1411          console.info("change font size");
1412        })
1413      List() {
1414        ForEach(this.styleList, (item: TextStyles) => {
1415          ListItem() {
1416            Text("Hello World")
1417              .fontSize(item.fontSize)
1418          }
1419        })
1420      }
1421    }
1422  }
1423}
1424```
1425
1426上述代码运行效果如下。
1427
1428![properly-use-state-management-to-develope-9](figures/properly-use-state-management-to-develope-9.gif)
1429
1430由于ForEach中生成的item是一个常量,因此当点击改变item中的内容时,没有办法观测到UI刷新,尽管日志表面item中的值已经改变了(这体现在打印了“change font size”的日志)。因此,需要使用自定义组件,配合@ObjectLink来实现观测的能力。
1431
1432```typescript
1433@Observed
1434class StyleList extends Array<TextStyles> {
1435};
1436@Observed
1437class TextStyles {
1438  fontSize: number;
1439
1440  constructor(fontSize: number) {
1441    this.fontSize = fontSize;
1442  }
1443}
1444@Component
1445struct TextComponent {
1446  @ObjectLink textStyle: TextStyles;
1447  build() {
1448    Text("Hello World")
1449      .fontSize(this.textStyle.fontSize)
1450  }
1451}
1452@Entry
1453@Component
1454struct Page {
1455  @State styleList: StyleList = new StyleList();
1456  aboutToAppear() {
1457    for (let i = 15; i < 50; i++)
1458      this.styleList.push(new TextStyles(i));
1459  }
1460  build() {
1461    Column() {
1462      Text("Font Size List")
1463        .fontSize(50)
1464        .onClick(() => {
1465          for (let i = 0; i < this.styleList.length; i++) {
1466            this.styleList[i].fontSize++;
1467          }
1468          console.info("change font size");
1469        })
1470      List() {
1471        ForEach(this.styleList, (item: TextStyles) => {
1472          ListItem() {
1473            TextComponent({ textStyle: item})
1474          }
1475        })
1476      }
1477    }
1478  }
1479}
1480```
1481
1482上述代码的运行效果如下。
1483
1484![properly-use-state-management-to-develope-10](figures/properly-use-state-management-to-develope-10.gif)
1485
1486使用@ObjectLink接受传入的item后,使得TextComponent组件内的textStyle变量具有了被观测的能力。在父组件更改styleList中的值时,由于@ObjectLink是引用传递,所以会观测到styleList每一个数据项的地址指向的对应item的fontSize的值被改变,因此触发UI的刷新。
1487
1488这是一个较为实用的使用状态管理进行刷新的开发方式。
1489
1490
1491
1492<!--no_check-->