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 131 132上述代码在页面加载时仅初始化加载三个列表元素,之后每点击一次列表元素,将增加一个列表元素。 133 134## 使用条件渲染替代显隐控制 135 136如下所示,开发者在使用visibility通用属性控制组件的显隐状态时,仍存在组件的重新创建过程,造成性能上的损耗。 137 138```ts 139@Entry 140@Component 141struct MyComponent { 142 @State isVisible: Visibility = Visibility.Visible; 143 144 build() { 145 Column() { 146 Button("显隐切换") 147 .onClick(() => { 148 if (this.isVisible == Visibility.Visible) { 149 this.isVisible = Visibility.None 150 } else { 151 this.isVisible = Visibility.Visible 152 } 153 }) 154 Row().visibility(this.isVisible) 155 .width(300).height(300).backgroundColor(Color.Pink) 156 }.width('100%') 157 } 158} 159``` 160 161要避免这一问题,可使用if条件渲染代替visibility属性变换,如下所示: 162 163```ts 164@Entry 165@Component 166struct MyComponent { 167 @State isVisible: boolean = true; 168 169 build() { 170 Column() { 171 Button("显隐切换") 172 .onClick(() => { 173 this.isVisible = !this.isVisible 174 }) 175 if (this.isVisible) { 176 Row() 177 .width(300).height(300).backgroundColor(Color.Pink) 178 } 179 }.width('100%') 180 } 181} 182``` 183 184 185 186## 使用Column/Row替代Flex 187 188由于Flex容器组件默认情况下存在shrink导致二次布局,这会在一定程度上造成页面渲染上的性能劣化。 189 190```ts 191@Entry 192@Component 193struct MyComponent { 194 build() { 195 Flex({ direction: FlexDirection.Column }) { 196 Flex().width(300).height(200).backgroundColor(Color.Pink) 197 Flex().width(300).height(200).backgroundColor(Color.Yellow) 198 Flex().width(300).height(200).backgroundColor(Color.Grey) 199 } 200 } 201} 202``` 203 204上述代码可将Flex替换为Column、Row,在保证实现的页面布局效果相同的前提下避免Flex二次布局带来的负面影响。 205 206```ts 207@Entry 208@Component 209struct MyComponent { 210 build() { 211 Column() { 212 Row().width(300).height(200).backgroundColor(Color.Pink) 213 Row().width(300).height(200).backgroundColor(Color.Yellow) 214 Row().width(300).height(200).backgroundColor(Color.Grey) 215 } 216 } 217} 218``` 219 220 221 222## 设置List组件的宽高 223 224开发者在使用Scroll容器组件嵌套List子组件时,若不指定List的宽高尺寸,则默认全部加载,如下所示: 225 226```ts 227@Entry 228@Component 229struct MyComponent { 230 private arr: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; 231 232 build() { 233 Scroll() { 234 List() { 235 ForEach(this.arr, (item) => { 236 ListItem() { 237 Text(`item value: ${item}`).fontSize(30).margin({ left: 10 }) 238 }.height(100) 239 }, (item) => item.toString()) 240 } 241 }.backgroundColor(Color.Pink) 242 } 243} 244``` 245 246因此,在这种场景下建议开发者设置List子组件的宽高,如下所示: 247 248```ts 249@Entry 250@Component 251struct MyComponent { 252 private arr: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; 253 254 build() { 255 Scroll() { 256 List() { 257 ForEach(this.arr, (item) => { 258 ListItem() { 259 Text(`item value: ${item}`).fontSize(30).margin({ left: 10 }) 260 }.height(100) 261 }, (item) => item.toString()) 262 }.width('100%').height(500) 263 }.backgroundColor(Color.Pink) 264 } 265} 266``` 267 268 269 270## 减少应用滑动白块 271 272应用通过增大List/Grid控件的cachedCount参数,调整UI的加载范围。cachedCount表示屏幕外List/Grid预加载item的个数。 273如果需要请求网络图片,可以在item滑动到屏幕显示之前,提前下载好内容,从而减少滑动白块。 274如下是使用cachedCount参数的例子: 275 276```ts 277@Entry 278@Component 279struct MyComponent { 280 private source: MyDataSource = new MyDataSource(); 281 282 build() { 283 List() { 284 LazyForEach(this.source, item => { 285 ListItem() { 286 Text("Hello" + item) 287 .fontSize(50) 288 .onAppear(() => { 289 console.log("appear:" + item) 290 }) 291 } 292 }) 293 }.cachedCount(3) // 扩大数值appear日志范围会变大 294 } 295} 296 297class MyDataSource implements IDataSource { 298 data: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; 299 300 public totalCount(): number { 301 return this.data.length 302 } 303 304 public getData(index: number): any { 305 return this.data[index] 306 } 307 308 registerDataChangeListener(listener: DataChangeListener): void { 309 } 310 311 unregisterDataChangeListener(listener: DataChangeListener): void { 312 } 313} 314``` 315 316 317**使用说明:** 318cachedCount的增加会增大UI的cpu、内存开销。使用时需要根据实际情况,综合性能和用户体验进行调整。