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 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 285