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