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.splice、Array.sort或Array.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 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