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