1# 性能提升的推荐方法 2 3开发者若使用低性能的代码实现功能场景可能不会影响应用的正常运行,但却会对应用的性能造成负面影响。本章节列举出了一些可提升性能的场景供开发者参考,以避免应用实现上带来的性能劣化。 4 5## 使用数据懒加载 6 7开发者在使用长列表时,如果直接采用循环渲染方式,如下所示,会一次性加载所有的列表元素,一方面会导致页面启动时间过长,影响用户体验,另一方面也会增加服务器的压力和流量,加重系统负担。 8 9```ts 10@Entry 11@Component 12struct MyComponent { 13 @State arr: number[] = Array.from(Array(100), (v,k) =>k); //构造0-99的数组 14 build() { 15 List() { 16 ForEach(this.arr, (item: number) => { 17 ListItem() { 18 Text(`item value: ${item}`) 19 } 20 }, (item: number) => item.toString()) 21 } 22 } 23} 24``` 25 26上述代码会在页面加载时将100个列表元素全部加载,这并非我们需要的,我们希望从数据源中按需迭代加载数据并创建相应组件,因此需要使用数据懒加载,如下所示: 27 28```ts 29class BasicDataSource implements IDataSource { 30 private listeners: DataChangeListener[] = [] 31 32 public totalCount(): number { 33 return 0 34 } 35 36 public getData(index: number): any { 37 return undefined 38 } 39 40 registerDataChangeListener(listener: DataChangeListener): void { 41 if (this.listeners.indexOf(listener) < 0) { 42 console.info('add listener') 43 this.listeners.push(listener) 44 } 45 } 46 47 unregisterDataChangeListener(listener: DataChangeListener): void { 48 const pos = this.listeners.indexOf(listener); 49 if (pos >= 0) { 50 console.info('remove listener') 51 this.listeners.splice(pos, 1) 52 } 53 } 54 55 notifyDataReload(): void { 56 this.listeners.forEach(listener => { 57 listener.onDataReloaded() 58 }) 59 } 60 61 notifyDataAdd(index: number): void { 62 this.listeners.forEach(listener => { 63 listener.onDataAdd(index) 64 }) 65 } 66 67 notifyDataChange(index: number): void { 68 this.listeners.forEach(listener => { 69 listener.onDataChange(index) 70 }) 71 } 72 73 notifyDataDelete(index: number): void { 74 this.listeners.forEach(listener => { 75 listener.onDataDelete(index) 76 }) 77 } 78 79 notifyDataMove(from: number, to: number): void { 80 this.listeners.forEach(listener => { 81 listener.onDataMove(from, to) 82 }) 83 } 84} 85 86class MyDataSource extends BasicDataSource { 87 private dataArray: string[] = ['item value: 0', 'item value: 1', 'item value: 2'] 88 89 public totalCount(): number { 90 return this.dataArray.length 91 } 92 93 public getData(index: number): any { 94 return this.dataArray[index] 95 } 96 97 public addData(index: number, data: string): void { 98 this.dataArray.splice(index, 0, data) 99 this.notifyDataAdd(index) 100 } 101 102 public pushData(data: string): void { 103 this.dataArray.push(data) 104 this.notifyDataAdd(this.dataArray.length - 1) 105 } 106} 107 108@Entry 109@Component 110struct MyComponent { 111 private data: MyDataSource = new MyDataSource() 112 113 build() { 114 List() { 115 LazyForEach(this.data, (item: string) => { 116 ListItem() { 117 Row() { 118 Text(item).fontSize(20).margin({ left: 10 }) 119 } 120 } 121 .onClick(() => { 122 this.data.pushData('item value: ' + this.data.totalCount()) 123 }) 124 }, item => item) 125 } 126 } 127} 128``` 129 130![LazyForEach1](figures/LazyForEach1.gif) 131 132上述代码在页面加载时仅初始化加载三个列表元素,之后每点击一次列表元素,将增加一个列表元素。 133 134## 设置List组件的宽高 135 136在使用Scroll容器组件嵌套List组件加载长列表时,若不指定List的宽高尺寸,则默认全部加载。 137 138> **说明:** 139> 140> Scroll嵌套List时: 141> 142> - List没有设置宽高,会布局List的所有子组件。 143> 144> - List设置宽高,会布局List显示区域内的子组件。 145> 146> - List使用[ForEach](../quick-start/arkts-rendering-control-foreach.md)加载子组件时,无论是否设置List的宽高,都会加载所有子组件。 147> 148> - List使用[LazyForEach](../quick-start/arkts-rendering-control-lazyforeach.md)加载子组件时,没有设置List的宽高,会加载所有子组件,设置了List的宽高,会加载List显示区域内的子组件。 149 150```ts 151class BasicDataSource implements IDataSource { 152 private listeners: DataChangeListener[] = [] 153 154 public totalCount(): number { 155 return 0 156 } 157 158 public getData(index: number): any { 159 return undefined 160 } 161 162 registerDataChangeListener(listener: DataChangeListener): void { 163 if (this.listeners.indexOf(listener) < 0) { 164 console.info('add listener') 165 this.listeners.push(listener) 166 } 167 } 168 169 unregisterDataChangeListener(listener: DataChangeListener): void { 170 const pos = this.listeners.indexOf(listener); 171 if (pos >= 0) { 172 console.info('remove listener') 173 this.listeners.splice(pos, 1) 174 } 175 } 176 177 notifyDataReload(): void { 178 this.listeners.forEach(listener => { 179 listener.onDataReloaded() 180 }) 181 } 182 183 notifyDataAdd(index: number): void { 184 this.listeners.forEach(listener => { 185 listener.onDataAdd(index) 186 }) 187 } 188 189 notifyDataChange(index: number): void { 190 this.listeners.forEach(listener => { 191 listener.onDataChange(index) 192 }) 193 } 194 195 notifyDataDelete(index: number): void { 196 this.listeners.forEach(listener => { 197 listener.onDataDelete(index) 198 }) 199 } 200 201 notifyDataMove(from: number, to: number): void { 202 this.listeners.forEach(listener => { 203 listener.onDataMove(from, to) 204 }) 205 } 206} 207 208class MyDataSource extends BasicDataSource { 209 private dataArray: Array<string> = new Array(100).fill('test') 210 211 public totalCount(): number { 212 return this.dataArray.length 213 } 214 215 public getData(index: number): any { 216 return this.dataArray[index] 217 } 218 219 public addData(index: number, data: string): void { 220 this.dataArray.splice(index, 0, data) 221 this.notifyDataAdd(index) 222 } 223 224 public pushData(data: string): void { 225 this.dataArray.push(data) 226 this.notifyDataAdd(this.dataArray.length - 1) 227 } 228} 229 230@Entry 231@Component 232struct MyComponent { 233 private data: MyDataSource = new MyDataSource() 234 235 build() { 236 Scroll() { 237 List() { 238 LazyForEach(this.data, (item: string, index: number) => { 239 ListItem() { 240 Row() { 241 Text('item value: ' + item + (index + 1)).fontSize(20).margin(10) 242 } 243 } 244 }) 245 } 246 } 247 } 248} 249``` 250 251因此,此场景下建议设置List子组件的宽高。 252 253```ts 254class BasicDataSource implements IDataSource { 255 private listeners: DataChangeListener[] = [] 256 257 public totalCount(): number { 258 return 0 259 } 260 261 public getData(index: number): any { 262 return undefined 263 } 264 265 registerDataChangeListener(listener: DataChangeListener): void { 266 if (this.listeners.indexOf(listener) < 0) { 267 console.info('add listener') 268 this.listeners.push(listener) 269 } 270 } 271 272 unregisterDataChangeListener(listener: DataChangeListener): void { 273 const pos = this.listeners.indexOf(listener); 274 if (pos >= 0) { 275 console.info('remove listener') 276 this.listeners.splice(pos, 1) 277 } 278 } 279 280 notifyDataReload(): void { 281 this.listeners.forEach(listener => { 282 listener.onDataReloaded() 283 }) 284 } 285 286 notifyDataAdd(index: number): void { 287 this.listeners.forEach(listener => { 288 listener.onDataAdd(index) 289 }) 290 } 291 292 notifyDataChange(index: number): void { 293 this.listeners.forEach(listener => { 294 listener.onDataChange(index) 295 }) 296 } 297 298 notifyDataDelete(index: number): void { 299 this.listeners.forEach(listener => { 300 listener.onDataDelete(index) 301 }) 302 } 303 304 notifyDataMove(from: number, to: number): void { 305 this.listeners.forEach(listener => { 306 listener.onDataMove(from, to) 307 }) 308 } 309} 310 311class MyDataSource extends BasicDataSource { 312 private dataArray: Array<string> = new Array(100).fill('test') 313 314 public totalCount(): number { 315 return this.dataArray.length 316 } 317 318 public getData(index: number): any { 319 return this.dataArray[index] 320 } 321 322 public addData(index: number, data: string): void { 323 this.dataArray.splice(index, 0, data) 324 this.notifyDataAdd(index) 325 } 326 327 public pushData(data: string): void { 328 this.dataArray.push(data) 329 this.notifyDataAdd(this.dataArray.length - 1) 330 } 331} 332 333@Entry 334@Component 335struct MyComponent { 336 private data: MyDataSource = new MyDataSource() 337 338 build() { 339 Scroll() { 340 List() { 341 LazyForEach(this.data, (item: string, index: number) => { 342 ListItem() { 343 Text('item value: ' + item + (index + 1)).fontSize(20).margin(10) 344 }.width('100%') 345 }) 346 }.width('100%').height(500) 347 }.backgroundColor(Color.Pink) 348 } 349} 350``` 351 352![list1](figures/list1.gif) 353 354## 使用条件渲染替代显隐控制 355 356如下所示,开发者在使用visibility通用属性控制组件的显隐状态时,仍存在组件的重新创建过程,造成性能上的损耗。 357 358```ts 359@Entry 360@Component 361struct MyComponent { 362 @State isVisible: Visibility = Visibility.Visible; 363 364 build() { 365 Column() { 366 Button("显隐切换") 367 .onClick(() => { 368 if (this.isVisible == Visibility.Visible) { 369 this.isVisible = Visibility.None 370 } else { 371 this.isVisible = Visibility.Visible 372 } 373 }) 374 Row().visibility(this.isVisible) 375 .width(300).height(300).backgroundColor(Color.Pink) 376 }.width('100%') 377 } 378} 379``` 380 381要避免这一问题,可使用if条件渲染代替visibility属性变换,如下所示: 382 383```ts 384@Entry 385@Component 386struct MyComponent { 387 @State isVisible: boolean = true; 388 389 build() { 390 Column() { 391 Button("显隐切换") 392 .onClick(() => { 393 this.isVisible = !this.isVisible 394 }) 395 if (this.isVisible) { 396 Row() 397 .width(300).height(300).backgroundColor(Color.Pink) 398 } 399 }.width('100%') 400 } 401} 402``` 403 404![isVisible](figures/isVisible.gif) 405 406## 使用Column/Row替代Flex 407 408由于Flex容器组件默认情况下存在shrink导致二次布局,这会在一定程度上造成页面渲染上的性能劣化。 409 410```ts 411@Entry 412@Component 413struct MyComponent { 414 build() { 415 Flex({ direction: FlexDirection.Column }) { 416 Flex().width(300).height(200).backgroundColor(Color.Pink) 417 Flex().width(300).height(200).backgroundColor(Color.Yellow) 418 Flex().width(300).height(200).backgroundColor(Color.Grey) 419 } 420 } 421} 422``` 423 424上述代码可将Flex替换为Column、Row,在保证实现的页面布局效果相同的前提下避免Flex二次布局带来的负面影响。 425 426```ts 427@Entry 428@Component 429struct MyComponent { 430 build() { 431 Column() { 432 Row().width(300).height(200).backgroundColor(Color.Pink) 433 Row().width(300).height(200).backgroundColor(Color.Yellow) 434 Row().width(300).height(200).backgroundColor(Color.Grey) 435 } 436 } 437} 438``` 439 440![flex1](figures/flex1.PNG) 441 442## 减少应用滑动白块 443 444应用通过增大List/Grid控件的cachedCount参数,调整UI的加载范围。cachedCount表示屏幕外List/Grid预加载item的个数。 445如果需要请求网络图片,可以在item滑动到屏幕显示之前,提前下载好内容,从而减少滑动白块。 446如下是使用cachedCount参数的例子: 447 448```ts 449@Entry 450@Component 451struct MyComponent { 452 private source: MyDataSource = new MyDataSource(); 453 454 build() { 455 List() { 456 LazyForEach(this.source, item => { 457 ListItem() { 458 Text("Hello" + item) 459 .fontSize(50) 460 .onAppear(() => { 461 console.log("appear:" + item) 462 }) 463 } 464 }) 465 }.cachedCount(3) // 扩大数值appear日志范围会变大 466 } 467} 468 469class MyDataSource implements IDataSource { 470 data: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; 471 472 public totalCount(): number { 473 return this.data.length 474 } 475 476 public getData(index: number): any { 477 return this.data[index] 478 } 479 480 registerDataChangeListener(listener: DataChangeListener): void { 481 } 482 483 unregisterDataChangeListener(listener: DataChangeListener): void { 484 } 485} 486``` 487![list2](figures/list2.gif) 488 489**使用说明:** 490cachedCount的增加会增大UI的cpu、内存开销。使用时需要根据实际情况,综合性能和用户体验进行调整。