• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 渲染控制
2
3ArkTS也提供了渲染控制的能力。条件渲染可根据应用的不同状态,渲染对应状态下的UI内容。循环渲染可从数据源中迭代获取数据,并在每次迭代过程中创建相应的组件。
4
5## 条件渲染
6
7使用if/else进行条件渲染。
8
9
10> **说明:**
11>
12> - if/else条件语句可以使用状态变量。
13>
14> - 使用if/else可以使子组件的渲染依赖条件语句。
15>
16> - 必须在容器组件内使用。
17>
18> - 某些容器组件限制子组件的类型或数量,将if/else用于这些组件内时,这些限制将同样应用于if/else语句内创建的组件。例如,Grid容器组件的子组件仅支持GridItem组件,在Grid内使用if/else时,则if/else语句内也仅允许使用GridItem组件。
19
20
21```ts
22Column() {
23  if (this.count < 0) {
24    Text('count is negative').fontSize(14)
25  } else if (this.count % 2 === 0) {
26    Text('count is even').fontSize(14)
27  } else {
28    Text('count is odd').fontSize(14)
29  }
30}
31```
32
33## 循环渲染
34
35通过循环渲染(ForEach)从数组中获取数据,并为每个数据项创建相应的组件,可减少代码复杂度。
36
37```ts
38ForEach(
39  arr: any[],
40  itemGenerator: (item: any, index?: number) => void,
41  keyGenerator?: (item: any, index?: number) => string
42)
43```
44
45从API version 9开始,该接口支持在ArkTS卡片中使用。
46
47**参数:**
48
49| 参数名        | 参数类型                              | 必填 | 参数描述                                                     |
50| ------------- | ------------------------------------- | ---- | ------------------------------------------------------------ |
51| arr           | any[]                                 | 是   | 必须是数组,允许设置为空数组,空数组场景下将不会创建子组件。同时允许设置返回值为数组类型的函数,例如arr.slice(1, 3),设置的函数不得改变包括数组本身在内的任何状态变量,如Array.spliceArray.sortArray.reverse这些改变原数组的函数。 |
52| itemGenerator | (item: any, index?: number) => void   | 是   | 生成子组件的lambda函数,为数组中的每一个数据项创建一个或多个子组件,单个子组件或子组件列表必须包括在大括号“{...}”中。 |
53| keyGenerator  | (item: any, index?: number) => string | 否   | 匿名函数,用于给数组中的每一个数据项生成唯一且固定的键值。当数据项在数组中的位置更改时,其键值不得更改,当数组中的数据项被新项替换时,被替换项的键值和新项的键值必须不同。键值生成器的功能是可选的,但是,为了使开发框架能够更好地识别数组更改,提高性能,建议提供。如将数组反向时,如果没有提供键值生成器,则ForEach中的所有节点都将重建。 |
54
55> **说明:**
56>
57> - ForEach必须在容器组件内使用。
58>
59> - 生成的子组件应当是允许包含在ForEach父容器组件中的子组件。
60>
61> - 允许子组件生成器函数中包含if/else条件渲染,同时也允许ForEach包含在if/else条件渲染语句中。
62>
63> - itemGenerator函数的调用顺序不一定和数组中的数据项相同,在开发过程中不要假设itemGenerator和keyGenerator函数是否执行及其执行顺序。例如,以下示例可能无法正常工作:
64>
65>   ```ts
66>   ForEach(anArray.map((item1, index1) => { return { i: index1 + 1, data: item1 }; }),
67>     item => Text(`${item.i}. item.data.label`),
68>     item => item.data.id.toString())
69>   ```
70
71## 示例
72
73```ts
74// xxx.ets
75@Entry
76@Component
77struct MyComponent {
78  @State arr: number[] = [10, 20, 30]
79
80  build() {
81    Column({ space: 5 }) {
82      Button('Reverse Array')
83        .onClick(() => {
84          this.arr.reverse()
85        })
86
87      ForEach(this.arr, (item: number) => {
88        Text(`item value: ${item}`).fontSize(18)
89        Divider().strokeWidth(2)
90      }, (item: number) => item.toString())
91    }
92  }
93}
94```
95
96![forEach1](figures/forEach1.gif)
97
98## 数据懒加载
99
100通过数据懒加载(LazyForEach)从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。
101
102```ts
103LazyForEach(
104  dataSource: IDataSource,
105  itemGenerator: (item: any) => void,
106  keyGenerator?: (item: any) => string
107): void
108
109interface IDataSource {
110  totalCount(): number;
111  getData(index: number): any;
112  registerDataChangeListener(listener: DataChangeListener): void;
113  unregisterDataChangeListener(listener: DataChangeListener): void;
114}
115
116interface DataChangeListener {
117  onDataReloaded(): void;
118  onDataAdd(index: number): void;
119  onDataMove(from: number, to: number): void;
120  onDataDelete(index: number): void;
121  onDataChange(index: number): void;
122}
123```
124
125**参数:**
126
127| 参数名        | 参数类型              | 必填 | 参数描述                                                     |
128| ------------- | --------------------- | ---- | ------------------------------------------------------------ |
129| dataSource    | IDataSource           | 是   | 实现IDataSource接口的对象,需要开发者实现相关接口。          |
130| itemGenerator | (item: any, index?: number) => void   | 是   | 生成子组件的lambda函数,为数组中的每一个数据项创建一个或多个子组件,单个子组件或子组件列表必须包括在大括号“{...}”中。 |
131| keyGenerator  | (item: any, index?: number) => string | 否   | 匿名函数,用于给数组中的每一个数据项生成唯一且固定的键值。当数据项在数组中的位置更改时,其键值不得更改,当数组中的数据项被新项替换时,被替换项的键值和新项的键值必须不同。键值生成器的功能是可选的,但是,为了使开发框架能够更好地识别数组更改,提高性能,建议提供。如将数组反向时,如果没有提供键值生成器,则LazyForEach中的所有节点都将重建。 |
132
133### IDataSource类型说明
134
135| 名称                                                         | 描述                   |
136| ------------------------------------------------------------ | ---------------------- |
137| totalCount(): number                                         | 获取数据总数。         |
138| getData(index: number): any                                  | 获取索引值index对应的数据。   |
139| registerDataChangeListener(listener:DataChangeListener): void | 注册数据改变的监听器。 |
140| unregisterDataChangeListener(listener:DataChangeListener): void | 注销数据改变的监听器。 |
141
142### DataChangeListener类型说明
143
144| 名称                                                     | 描述                                   |
145| -------------------------------------------------------- | -------------------------------------- |
146| onDataReloaded(): void                                   | 重新加载所有数据。                     |
147| onDataAdded(index: number): void<sup>deprecated</sup>           | 通知组件index的位置有数据添加。从API Version 8开始废弃,建议使用onDataAdd。       |
148| onDataMoved(from: number, to: number): void<sup>deprecated</sup> | 通知组件数据从from的位置移到to的位置。从API Version 8开始废弃,建议使用onDataMove。 |
149| onDataDeleted(index: number): void<sup>deprecated</sup>         | 通知组件index的位置有数据删除。从API Version 8开始废弃,建议使用onDataDelete。        |
150| onDataChanged(index: number): void<sup>deprecated</sup>          | 通知组件index的位置有数据变化。 从API Version 8开始废弃,建议使用onDataChange。       |
151| onDataAdd(index: number): void<sup>8+</sup>                     | 通知组件index的位置有数据添加。        |
152| onDataMove(from: number, to: number): void<sup>8+</sup>           | 通知组件数据从from的位置移到to的位置。 |
153| onDataDelete(index: number): void<sup>8+</sup>                     | 通知组件index的位置有数据删除。        |
154| onDataChange(index: number): void<sup>8+</sup>                     | 通知组件index的位置有数据变化。        |
155
156## 示例
157
158```ts
159// xxx.ets
160class BasicDataSource implements IDataSource {
161  private listeners: DataChangeListener[] = []
162
163  public totalCount(): number {
164    return 0
165  }
166
167  public getData(index: number): any {
168    return undefined
169  }
170
171  registerDataChangeListener(listener: DataChangeListener): void {
172    if (this.listeners.indexOf(listener) < 0) {
173      console.info('add listener')
174      this.listeners.push(listener)
175    }
176  }
177
178  unregisterDataChangeListener(listener: DataChangeListener): void {
179    const pos = this.listeners.indexOf(listener);
180    if (pos >= 0) {
181      console.info('remove listener')
182      this.listeners.splice(pos, 1)
183    }
184  }
185
186  notifyDataReload(): void {
187    this.listeners.forEach(listener => {
188      listener.onDataReloaded()
189    })
190  }
191
192  notifyDataAdd(index: number): void {
193    this.listeners.forEach(listener => {
194      listener.onDataAdd(index)
195    })
196  }
197
198  notifyDataChange(index: number): void {
199    this.listeners.forEach(listener => {
200      listener.onDataChange(index)
201    })
202  }
203
204  notifyDataDelete(index: number): void {
205    this.listeners.forEach(listener => {
206      listener.onDataDelete(index)
207    })
208  }
209
210  notifyDataMove(from: number, to: number): void {
211    this.listeners.forEach(listener => {
212      listener.onDataMove(from, to)
213    })
214  }
215}
216
217class MyDataSource extends BasicDataSource {
218  // 初始化数据列表
219  private dataArray: string[] = ['/path/image0.png', '/path/image1.png', '/path/image2.png', '/path/image3.png']
220
221  public totalCount(): number {
222    return this.dataArray.length
223  }
224
225  public getData(index: number): any {
226    return this.dataArray[index]
227  }
228
229  public addData(index: number, data: string): void {
230    this.dataArray.splice(index, 0, data)
231    this.notifyDataAdd(index)
232  }
233
234  public pushData(data: string): void {
235    this.dataArray.push(data)
236    this.notifyDataAdd(this.dataArray.length - 1)
237  }
238}
239
240@Entry
241@Component
242struct MyComponent {
243  private data: MyDataSource = new MyDataSource()
244
245  build() {
246    List({ space: 3 }) {
247      LazyForEach(this.data, (item: string) => {
248        ListItem() {
249          Row() {
250            Image(item).width(50).height(50)
251            Text(item).fontSize(20).margin({ left: 10 })
252          }.margin({ left: 10, right: 10 })
253        }
254        .onClick(() => {
255          // 每点击一次列表项,数据增加一项
256          this.data.pushData('/path/image' + this.data.totalCount() + '.png')
257        })
258      }, item => item)
259    }.height('100%').width('100%')
260  }
261}
262```
263
264> **说明:**
265>
266> - LazyForEach必须在容器组件内使用,目前仅有List、Grid以及Swiper组件支持数据懒加载(即只加载可视部分以及其前后少量数据用于缓冲),其他组件仍然是一次性加载所有的数据。
267>
268> - LazyForEach在每次迭代中,必须创建且只允许创建一个子组件。
269>
270> - 生成的子组件必须是允许包含在LazyForEach父容器组件中的子组件。
271>
272> - 允许LazyForEach包含在if/else条件渲染语句中。
273>
274> - 为了高性能渲染,通过DataChangeListener对象的onDataChange方法来更新UI时,仅当itemGenerator中创建的子组件内使用了状态变量时,才会触发组件刷新。
275>
276> - itemGenerator函数的调用顺序不一定和数据源中的数据项相同,在开发过程中不要假设itemGenerator和keyGenerator函数是否执行及其执行顺序。例如,以下示例可能无法正常工作:
277>
278>   ```ts
279>   LazyForEach(dataSource,
280>     item => Text(`${item.i}. item.data.label`),
281>     item => item.data.id.toString())
282>   ```
283
284![lazyForEach](figures/lazyForEach.gif)