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