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.realState1、this.realState2没有被装饰,他们的变化将不会触发UI刷新。 147 148- 但是在该应用中,用户试图通过this.needsUpdate的更新来带动常规变量this.realState1、this.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