• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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## 使用条件渲染替代显隐控制
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![isVisible](figures/isVisible.gif)
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![flex1](figures/flex1.PNG)
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![list1](figures/list1.gif)
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![list2](figures/list2.gif)
316
317**使用说明:**
318cachedCount的增加会增大UI的cpu、内存开销。使用时需要根据实际情况,综合性能和用户体验进行调整。