• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 状态管理优秀实践
2
3
4为了帮助应用程序开发人员提高其应用程序质量,特别是在高效的状态管理方面。本章节面向开发者提供了多个在开发ArkUI应用中常见的低效开发的场景,并给出了对应的解决方案。此外,还提供了同一场景下,推荐用法和不推荐用法的对比和解释说明,更直观地展示两者区别,从而帮助开发者学习如何正确地在应用开发中使用状态变量,进行高性能开发。
5
6## 使用@ObjectLink代替@Prop减少不必要的深拷贝
7
8在应用开发中,开发者经常会进行父子组件的数值传递,而在不会改变子组件内状态变量值的情况下,使用@Prop装饰状态变量会导致组件创建的耗时增加,从而影响一部分性能。
9
10【反例】
11
12```ts
13@Observed
14class ClassA {
15  public c: number = 0;
16
17  constructor(c: number) {
18    this.c = c;
19  }
20}
21
22@Component
23struct PropChild {
24  @Prop testNum: ClassA; // @Prop 装饰状态变量会深拷贝
25
26  build() {
27    Text(`PropChild testNum ${this.testNum.c}`)
28  }
29}
30
31@Entry
32@Component
33struct Parent {
34  @State testNum: ClassA[] = [new ClassA(1)];
35
36  build() {
37    Column() {
38      Text(`Parent testNum ${this.testNum[0].c}`)
39        .onClick(() => {
40          this.testNum[0].c += 1;
41        })
42
43      // PropChild没有改变@Prop testNum: ClassA的值,所以这时最优的选择是使用@ObjectLink
44      PropChild({ testNum: this.testNum[0] })
45    }
46  }
47}
48```
49
50在上文的示例中,PropChild组件没有改变\@Prop testNum: ClassA的值,所以这时较优的选择是使用\@ObjectLink,因为\@Prop是会深拷贝数据,具有拷贝的性能开销,所以这个时候\@ObjectLink是比\@Link和\@Prop更优的选择。
51
52【正例】
53
54```ts
55@Observed
56class ClassA {
57  public c: number = 0;
58
59  constructor(c: number) {
60    this.c = c;
61  }
62}
63
64@Component
65struct PropChild {
66  @ObjectLink testNum: ClassA; // @ObjectLink 装饰状态变量不会深拷贝
67
68  build() {
69    Text(`PropChild testNum ${this.testNum.c}`)
70  }
71}
72
73@Entry
74@Component
75struct Parent {
76  @State testNum: ClassA[] = [new ClassA(1)];
77
78  build() {
79    Column() {
80      Text(`Parent testNum ${this.testNum[0].c}`)
81        .onClick(() => {
82          this.testNum[0].c += 1;
83        })
84
85      // 当子组件不需要发生本地改变时,优先使用@ObjectLink,因为@Prop是会深拷贝数据,具有拷贝的性能开销,所以这个时候@ObjectLink是比@Link和@Prop更优的选择
86      PropChild({ testNum: this.testNum[0] })
87    }
88  }
89}
90```
91
92
93## 不使用状态变量强行更新非状态变量关联组件
94
95【反例】
96
97
98```ts
99@Entry
100@Component
101struct CompA {
102  @State needsUpdate: boolean = true;
103  realState1: Array<number> = [4, 1, 3, 2]; // 未使用状态变量装饰器
104  realState2: Color = Color.Yellow;
105
106  updateUI1(param: Array<number>): Array<number> {
107    const triggerAGet = this.needsUpdate;
108    return param;
109  }
110  updateUI2(param: Color): Color {
111    const triggerAGet = this.needsUpdate;
112    return param;
113  }
114  build() {
115    Column({ space: 20 }) {
116      ForEach(this.updateUI1(this.realState1),
117        (item: Array<number>) => {
118          Text(`${item}`)
119        })
120      Text("add item")
121        .onClick(() => {
122          // 改变realState1不会触发UI视图更新
123          this.realState1.push(this.realState1[this.realState1.length-1] + 1);
124
125          // 触发UI视图更新
126          this.needsUpdate = !this.needsUpdate;
127        })
128      Text("chg color")
129        .onClick(() => {
130          // 改变realState2不会触发UI视图更新
131          this.realState2 = this.realState2 == Color.Yellow ? Color.Red : Color.Yellow;
132
133          // 触发UI视图更新
134          this.needsUpdate = !this.needsUpdate;
135        })
136    }.backgroundColor(this.updateUI2(this.realState2))
137    .width(200).height(500)
138  }
139}
140```
141
142上述示例存在以下问题:
143
144- 应用程序希望控制UI更新逻辑,但在ArkUI中,UI更新的逻辑应该是由框架来检测应用程序状态变量的更改去实现。
145
146- this.needsUpdate是一个自定义的UI状态变量,应该仅应用于其绑定的UI组件。变量this.realState1this.realState2没有被装饰,他们的变化将不会触发UI刷新。
147
148- 但是在该应用中,用户试图通过this.needsUpdate的更新来带动常规变量this.realState1this.realState2的更新,此方法不合理且更新性能较差。
149
150【正例】
151
152要解决此问题,应将realState1和realState2成员变量用\@State装饰。一旦完成此操作,就不再需要变量needsUpdate。
153
154
155```ts
156@Entry
157@Component
158struct CompA {
159  @State realState1: Array<number> = [4, 1, 3, 2];
160  @State realState2: Color = Color.Yellow;
161  build() {
162    Column({ space: 20 }) {
163      ForEach(this.realState1,
164        (item: Array<number>) => {
165          Text(`${item}`)
166        })
167      Text("add item")
168        .onClick(() => {
169          // 改变realState1触发UI视图更新
170          this.realState1.push(this.realState1[this.realState1.length-1] + 1);
171        })
172      Text("chg color")
173        .onClick(() => {
174          // 改变realState2触发UI视图更新
175          this.realState2 = this.realState2 == Color.Yellow ? Color.Red : Color.Yellow;
176        })
177    }.backgroundColor(this.realState2)
178    .width(200).height(500)
179  }
180}
181```
182
183## 精准控制状态变量关联的组件数
184
185精准控制状态变量关联的组件数能减少不必要的组件刷新,提高组件的刷新效率。有时开发者会将同一个状态变量绑定多个同级组件的属性,当状态变量改变时,会让这些组件做出相同的改变,这有时会造成组件的不必要刷新,如果存在某些比较复杂的组件,则会大大影响整体的性能。但是如果将这个状态变量绑定在这些同级组件的父组件上,则可以减少需要刷新的组件数,从而提高刷新的性能。
186
187【反例】
188
189```ts
190@Observed
191class Translate {
192  translateX: number = 20;
193}
194@Component
195struct Title {
196  @ObjectLink translateObj: Translate;
197  build() {
198    Row() {
199      Image($r('app.media.icon'))
200        .width(50)
201        .height(50)
202        .translate({
203          x:this.translateObj.translateX // this.translateObj.translateX used in two component both in Row
204        })
205      Text("Title")
206        .fontSize(20)
207        .translate({
208          x: this.translateObj.translateX
209        })
210    }
211  }
212}
213@Entry
214@Component
215struct Page {
216  @State translateObj: Translate = new Translate();
217  build() {
218    Column() {
219      Title({
220        translateObj: this.translateObj
221      })
222      Stack() {
223      }
224      .backgroundColor("black")
225      .width(200)
226      .height(400)
227      .translate({
228        x:this.translateObj.translateX //this.translateObj.translateX used in two components both in Column
229      })
230      Button("move")
231        .translate({
232          x:this.translateObj.translateX
233        })
234        .onClick(() => {
235          animateTo({
236            duration: 50
237          },()=>{
238            this.translateObj.translateX = (this.translateObj.translateX + 50) % 150
239          })
240        })
241    }
242  }
243}
244```
245
246在上面的示例中,状态变量this.translateObj.translateX被用在多个同级的子组件下,当this.translateObj.translateX变化时,会导致所有关联它的组件一起刷新,但实际上由于这些组件的变化是相同的,因此可以将这个属性绑定到他们共同的父组件上,来实现减少组件的刷新数量。经过分析,所有的子组件其实都处于Page下的Column中,因此将所有子组件相同的translate属性统一到Column上,来实现精准控制状态变量关联的组件数。
247
248【正例】
249
250```ts
251@Observed
252class Translate {
253  translateX: number = 20;
254}
255@Component
256struct Title {
257  build() {
258    Row() {
259      Image($r('app.media.icon'))
260        .width(50)
261        .height(50)
262      Text("Title")
263        .fontSize(20)
264    }
265  }
266}
267@Entry
268@Component
269struct Page1 {
270  @State translateObj: Translate = new Translate();
271  build() {
272    Column() {
273      Title()
274      Stack() {
275      }
276      .backgroundColor("black")
277      .width(200)
278      .height(400)
279      Button("move")
280        .onClick(() => {
281          animateTo({
282            duration: 50
283          },()=>{
284            this.translateObj.translateX = (this.translateObj.translateX + 50) % 150
285          })
286        })
287    }
288    .translate({ // the component in Column shares the same property translate
289      x: this.translateObj.translateX
290    })
291  }
292}
293```
294
295