• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Rendering Control
2
3ArkTS provides conditional rendering and loop rendering. Conditional rendering can render state-specific UI content based on the application status. Loop rendering iteratively obtains data from the data source and creates the corresponding component during each iteration.
4
5## Conditional Rendering
6
7Use **if/else** for conditional rendering.
8
9
10> **NOTE**
11>
12> - State variables can be used in the **if/else** statement.
13>
14> - The **if/else** statement can be used to implement rendering of child components.
15>
16> - The **if/else** statement must be used in container components.
17>
18> - Some container components limit the type or number of subcomponents. When **if/else** is placed in these components, the limitation applies to components created in **if/else** statements. For example, when **if/else** is used in the **\<Grid>** container component, whose child components can only be **\<GridItem>**, only the **\<GridItem>** component can be used in the **if/else** statement.
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## Loop Rendering
34
35You can use **ForEach** to obtain data from arrays and create components for each data item.
36
37```ts
38ForEach(
39  arr: any[],
40  itemGenerator: (item: any, index?: number) => void,
41  keyGenerator?: (item: any, index?: number) => string
42)
43```
44
45Since API version 9, this API is supported in ArkTS widgets.
46
47**Parameters**
48
49| Name       | Type                             | Mandatory| Description                                                    |
50| ------------- | ------------------------------------- | ---- | ------------------------------------------------------------ |
51| arr           | any[]                                 | Yes  | An array, which can be empty, in which case no child component is created. The functions that return array-type values are also allowed, for example, **arr.slice (1, 3)**. The set functions cannot change any state variables including the array itself, such as **Array.splice**, **Array.sort**, and **Array.reverse**.|
52| itemGenerator | (item: any, index?: number) => void   | Yes  | A lambda function used to generate one or more child components for each data item in an array. A single child component or a list of child components must be included in parentheses.|
53| keyGenerator  | (item: any, index?: number) => string | No  | An anonymous function used to generate a unique and fixed key value for each data item in an array. This key value must remain unchanged for the data item even when the item is relocated in the array. When the item is replaced by a new item, the key value of the new item must be different from that of the existing item. This key-value generator is optional. However, for performance reasons, it is strongly recommended that the key-value generator be provided, so that the development framework can better identify array changes. For example, if no key-value generator is provided, a reverse of an array will result in rebuilding of all nodes in **ForEach**.|
54
55> **NOTE**
56>
57> - **ForEach** must be used in container components.
58>
59> - The generated child components should be allowed in the parent container component of **ForEach**.
60>
61> - The **itemGenerator** function can contain an **if/else** statement, and an **if/else** statement can contain **ForEach**.
62>
63> - The call sequence of **itemGenerator** functions may be different from that of the data items in the array. During the development, do not assume whether or when the **itemGenerator** and **keyGenerator** functions are executed. The following is an example of incorrect usage:
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## Example
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## Lazy Loading
99
100You can use **LazyForEach** to iterate over provided data sources and create corresponding components during each iteration.
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**Parameters**
126
127| Name       | Type             | Mandatory| Description                                                    |
128| ------------- | --------------------- | ---- | ------------------------------------------------------------ |
129| dataSource    | IDataSource           | Yes  | Object used to implement the **IDataSource** API. You need to implement related APIs.         |
130| itemGenerator | (item: any, index?: number) => void   | Yes  | A lambda function used to generate one or more child components for each data item in an array. A single child component or a list of child components must be included in parentheses.|
131| keyGenerator  | (item: any, index?: number) => string | No  | An anonymous function used to generate a unique and fixed key value for each data item in an array. This key value must remain unchanged for the data item even when the item is relocated in the array. When the item is replaced by a new item, the key value of the new item must be different from that of the existing item. This key-value generator is optional. However, for performance reasons, it is strongly recommended that the key-value generator be provided, so that the development framework can better identify array changes. For example, if no key-value generator is provided, a reverse of an array will result in rebuilding of all nodes in **LazyForEach**.|
132
133### Description of IDataSource
134
135| Name                                                        | Description                  |
136| ------------------------------------------------------------ | ---------------------- |
137| totalCount(): number                                         | Obtains the total number of data records.        |
138| getData(index: number): any                                  | Obtains the data corresponding to the specified index.  |
139| registerDataChangeListener(listener:DataChangeListener): void | Registers a listener for data changes.|
140| unregisterDataChangeListener(listener:DataChangeListener): void | Deregisters a listener for data changes.|
141
142### Description of DataChangeListener
143
144| Name                                                    | Description                                  |
145| -------------------------------------------------------- | -------------------------------------- |
146| onDataReloaded(): void                                   | Invoked when all data is reloaded.                    |
147| onDataAdded(index: number): void<sup>deprecated</sup>           | Invoked when data is added to the position indicated by the specified index. This API is deprecated since API version 8. You are advised to use **onDataAdd**.      |
148| onDataMoved(from: number, to: number): void<sup>deprecated</sup> | Invoked when data is moved from the **from** position to the **to** position. This API is deprecated since API version 8. You are advised to use **onDataMove**.|
149| onDataDeleted(index: number): void<sup>deprecated</sup>         | Invoked when data is deleted from the position indicated by the specified index. This API is deprecated since API version 8. You are advised to use **onDataDelete**.       |
150| onDataChanged(index: number): void<sup>deprecated</sup>          | Invoked when data in the position indicated by the specified index is changed. This API is deprecated since API version 8. You are advised to use **onDataChange**.      |
151| onDataAdd(index: number): void<sup>8+</sup>                     | Invoked when data is added to the position indicated by the specified index.       |
152| onDataMove(from: number, to: number): void<sup>8+</sup>           | Invoked when data is moved from the **from** position to the **to** position.|
153| onDataDelete(index: number): void<sup>8+</sup>                     | Invoked when data is deleted from the position indicated by the specified index.       |
154| onDataChange(index: number): void<sup>8+</sup>                     | Invoked when data in the position indicated by the specified index is changed.       |
155
156## Example
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  // Initialize the data list.
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          // The count increases by one each time the list is clicked.
256          this.data.pushData('/path/image' + this.data.totalCount() + '.png')
257        })
258      }, item => item)
259    }
260  }
261}
262```
263
264> **NOTE**
265>
266> - **LazyForEach** must be used in the container component. Currently, only the **\<List>**, **\<Grid>**, and **\<Swiper>** components support lazy loading (that is, only the visible part and a small amount of data before and after the visible part are loaded for caching). For other components, all data is loaded at a time.
267>
268> - **LazyForEach** must create one and only one child component in each iteration.
269>
270> - The generated child components must be the ones allowed in the parent container component of **LazyForEach**.
271>
272> - **LazyForEach** can be included in an **if/else** statement.
273>
274> - For the purpose of high-performance rendering, when the **onDataChange** method of the **DataChangeListener** object is used to update the UI, the component update is triggered only when the state variable is used in the child component created by **itemGenerator**.
275>
276> - The call sequence of **itemGenerator** functions may be different from that of the data items in the data source. During the development, do not assume whether or when the **itemGenerator** and **keyGenerator** functions are executed. The following is an example of incorrect usage:
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)
285