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