arkts-rendering-control.md 11.3 KB
Newer Older
T
tianyu 已提交
1 2
# 渲染控制

L
luoying_ace_admin 已提交
3
ArkTS也提供了渲染控制的能力。条件渲染可根据应用的不同状态,渲染对应状态下的UI内容。循环渲染可从数据源中迭代获取数据,并在每次迭代过程中创建相应的组件。
T
tianyu 已提交
4 5 6 7 8 9 10 11

## 条件渲染

使用if/else进行条件渲染。


> **说明:**
>
L
luoying_ace_admin 已提交
12
> - if/else条件语句可以使用状态变量。
T
tianyu 已提交
13
>
L
luoying_ace_admin 已提交
14
> - 使用if/else可以使子组件的渲染依赖条件语句。
T
tianyu 已提交
15 16 17
>
> - 必须在容器组件内使用。
>
L
luoying_ace_admin 已提交
18
> - 某些容器组件限制子组件的类型或数量,将if/else用于这些组件内时,这些限制将同样应用于if/else语句内创建的组件。例如,Grid容器组件的子组件仅支持GridItem组件,在Grid内使用if/else时,则if/else语句内也仅允许使用GridItem组件。
T
tianyu 已提交
19 20 21 22


```ts
Column() {
L
luoying_ace_admin 已提交
23 24 25 26 27 28 29
  if (this.count < 0) {
    Text('count is negative').fontSize(14)
  } else if (this.count % 2 === 0) {
    Text('count is even').fontSize(14)
  } else {
    Text('count is odd').fontSize(14)
  }
T
tianyu 已提交
30 31 32 33 34
}
```

## 循环渲染

L
luoying_ace_admin 已提交
35
通过循环渲染(ForEach)从数组中获取数据,并为每个数据项创建相应的组件,可减少代码复杂度。
T
tianyu 已提交
36 37

```
L
luoying_ace_admin 已提交
38 39 40 41 42
ForEach(
  arr: any[], 
  itemGenerator: (item: any, index?: number) => void,
  keyGenerator?: (item: any, index?: number) => string 
)
T
tianyu 已提交
43 44 45 46 47 48
```

**参数:**

| 参数名        | 参数类型                              | 必填 | 参数描述                                                     |
| ------------- | ------------------------------------- | ---- | ------------------------------------------------------------ |
L
luoying_ace_admin 已提交
49 50 51
| arr           | any[]                                 | 是   | 必须是数组,允许设置为空数组,空数组场景下将不会创建子组件。同时允许设置返回值为数组类型的函数,例如arr.slice(1, 3),设置的函数不得改变包括数组本身在内的任何状态变量,如Array.splice、Array.sort或Array.reverse这些改变原数组的函数。 |
| itemGenerator | (item: any, index?: number) => void   | 是   | 生成子组件的lambda函数,为数组中的每一个数据项创建一个或多个子组件,单个子组件或子组件列表必须包括在大括号“{...}”中。 |
| keyGenerator  | (item: any, index?: number) => string | 否   | 匿名函数,用于给数组中的每一个数据项生成唯一且固定的键值。当数据项在数组中的位置更改时,其键值不得更改,当数组中的数据项被新项替换时,被替换项的键值和新项的键值必须不同。键值生成器的功能是可选的,但是,为了使开发框架能够更好地识别数组更改,提高性能,建议提供。如将数组反向时,如果没有提供键值生成器,则ForEach中的所有节点都将重建。 |
T
tianyu 已提交
52 53 54

> **说明:**
>
L
luoying_ace_admin 已提交
55
> - ForEach必须在容器组件内使用。
T
tianyu 已提交
56
>
L
luoying_ace_admin 已提交
57
> - 生成的子组件应当是允许包含在ForEach父容器组件中的子组件。
T
tianyu 已提交
58
>
L
luoying_ace_admin 已提交
59
> - 允许子组件生成器函数中包含if/else条件渲染,同时也允许ForEach包含在if/else条件渲染语句中。
T
tianyu 已提交
60
>
L
luoying_ace_admin 已提交
61 62 63
> - itemGenerator函数的调用顺序不一定和数组中的数据项相同,在开发过程中不要假设itemGenerator和keyGenerator函数是否执行及其执行顺序。例如,以下示例可能无法正常工作:
>
>   ```ts
T
tianyu 已提交
64
>   ForEach(anArray.map((item1, index1) => { return { i: index1 + 1, data: item1 }; }), 
L
luoying_ace_admin 已提交
65 66
>     item => Text(`${item.i}. item.data.label`),
>     item => item.data.id.toString())
T
tianyu 已提交
67 68 69 70 71 72 73 74 75 76 77 78
>   ```

## 示例

```ts
// xxx.ets
@Entry
@Component
struct MyComponent {
  @State arr: number[] = [10, 20, 30]

  build() {
L
luoying_ace_admin 已提交
79 80 81 82 83 84 85 86 87 88
    Column({ space: 5 }) {
      Button('Reverse Array')
        .onClick(() => {
          this.arr.reverse()
        })

      ForEach(this.arr, (item: number) => {
        Text(`item value: ${item}`).fontSize(18)
        Divider().strokeWidth(2)
      }, (item: number) => item.toString())
T
tianyu 已提交
89 90 91 92 93
    }
  }
}
```

Y
yamila 已提交
94 95
![forEach1](figures/forEach1.gif)

T
tianyu 已提交
96 97
## 数据懒加载

L
luoying_ace_admin 已提交
98
通过数据懒加载(LazyForEach)从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。
T
tianyu 已提交
99 100 101

```ts
LazyForEach(
L
luoying_ace_admin 已提交
102 103 104
  dataSource: IDataSource,             
  itemGenerator: (item: any) => void,  
  keyGenerator?: (item: any) => string 
T
tianyu 已提交
105 106 107
): void

interface IDataSource {
L
luoying_ace_admin 已提交
108 109 110 111
  totalCount(): number;                                           
  getData(index: number): any;                                   
  registerDataChangeListener(listener: DataChangeListener): void;   
  unregisterDataChangeListener(listener: DataChangeListener): void;
T
tianyu 已提交
112 113 114
}

interface DataChangeListener {
L
luoying_ace_admin 已提交
115 116 117 118 119
  onDataReloaded(): void;                      
  onDataAdd(index: number): void;            
  onDataMove(from: number, to: number): void; 
  onDataDelete(index: number): void;         
  onDataChange(index: number): void;          
T
tianyu 已提交
120 121 122 123 124 125 126 127
}
```

**参数:**

| 参数名        | 参数类型              | 必填 | 参数描述                                                     |
| ------------- | --------------------- | ---- | ------------------------------------------------------------ |
| dataSource    | IDataSource           | 是   | 实现IDataSource接口的对象,需要开发者实现相关接口。          |
L
luoying_ace_admin 已提交
128 129
| itemGenerator | (item: any) => void   | 是   | 生成子组件的lambda函数,为数组中的每一个数据项创建一个或多个子组件,单个子组件或子组件列表必须包括在大括号“{...}”中。 |
| keyGenerator  | (item: any) => string | 否   | 匿名函数,用于给数组中的每一个数据项生成唯一且固定的键值。当数据项在数组中的位置更改时,其键值不得更改,当数组中的数据项被新项替换时,被替换项的键值和新项的键值必须不同。键值生成器的功能是可选的,但是,为了使开发框架能够更好地识别数组更改,提高性能,建议提供。如将数组反向时,如果没有提供键值生成器,则LazyForEach中的所有节点都将重建。 |
T
tianyu 已提交
130

L
luoying_ace_admin 已提交
131
### IDataSource类型说明
T
tianyu 已提交
132 133 134 135

| 名称                                                         | 描述                   |
| ------------------------------------------------------------ | ---------------------- |
| totalCount(): number                                         | 获取数据总数。         |
L
luoying_ace_admin 已提交
136 137 138
| getData(index: number): any                                  | 获取索引值index对应的数据。   |
| registerDataChangeListener(listener:DataChangeListener): void | 注册数据改变的监听器。 |
| unregisterDataChangeListener(listener:DataChangeListener): void | 注销数据改变的监听器。 |
T
tianyu 已提交
139

L
luoying_ace_admin 已提交
140
### DataChangeListener类型说明
T
tianyu 已提交
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156

| 名称                                                     | 描述                                   |
| -------------------------------------------------------- | -------------------------------------- |
| onDataReloaded(): void                                   | 重新加载所有数据。                     |
| onDataAdded(index: number): void (deprecated)            | 通知组件index的位置有数据添加。        |
| onDataMoved(from: number, to: number): void (deprecated) | 通知组件数据从from的位置移到to的位置。 |
| onDataDeleted(index: number): void (deprecated)          | 通知组件index的位置有数据删除。        |
| onDataChanged(index: number): void (deprecated)          | 通知组件index的位置有数据变化。        |
| onDataAdd(index: number): void 8+                        | 通知组件index的位置有数据添加。        |
| onDataMove(from: number, to: number): void 8+            | 通知组件数据从from的位置移到to的位置。 |
| onDataDelete(index: number): void 8+                     | 通知组件index的位置有数据删除。        |
| onDataChange(index: number): void 8+                     | 通知组件index的位置有数据变化。        |

## 示例

```ts
L
luoying_ace_admin 已提交
157
// xxx.ets
T
tianyu 已提交
158
class BasicDataSource implements IDataSource {
L
luoying_ace_admin 已提交
159
  private listeners: DataChangeListener[] = []
T
tianyu 已提交
160

L
luoying_ace_admin 已提交
161 162 163
  public totalCount(): number {
    return 0
  }
T
tianyu 已提交
164

L
luoying_ace_admin 已提交
165 166 167
  public getData(index: number): any {
    return undefined
  }
T
tianyu 已提交
168

L
luoying_ace_admin 已提交
169 170 171 172
  registerDataChangeListener(listener: DataChangeListener): void {
    if (this.listeners.indexOf(listener) < 0) {
      console.info('add listener')
      this.listeners.push(listener)
T
tianyu 已提交
173
    }
L
luoying_ace_admin 已提交
174 175 176 177 178 179 180
  }

  unregisterDataChangeListener(listener: DataChangeListener): void {
    const pos = this.listeners.indexOf(listener);
    if (pos >= 0) {
      console.info('remove listener')
      this.listeners.splice(pos, 1)
T
tianyu 已提交
181
    }
L
luoying_ace_admin 已提交
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
  }

  notifyDataReload(): void {
    this.listeners.forEach(listener => {
      listener.onDataReloaded()
    })
  }

  notifyDataAdd(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataAdd(index)
    })
  }

  notifyDataChange(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataChange(index)
    })
  }

  notifyDataDelete(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataDelete(index)
    })
  }

  notifyDataMove(from: number, to: number): void {
    this.listeners.forEach(listener => {
      listener.onDataMove(from, to)
    })
  }
T
tianyu 已提交
213 214 215
}

class MyDataSource extends BasicDataSource {
L
luoying_ace_admin 已提交
216
  // 初始化数据列表
Y
yamila 已提交
217
  private dataArray: string[] = ['/path/image0.png', '/path/image1.png', '/path/image2.png', '/path/image3.png']
T
tianyu 已提交
218

L
luoying_ace_admin 已提交
219 220 221
  public totalCount(): number {
    return this.dataArray.length
  }
T
tianyu 已提交
222

L
luoying_ace_admin 已提交
223 224 225 226 227 228 229 230 231 232 233 234 235
  public getData(index: number): any {
    return this.dataArray[index]
  }

  public addData(index: number, data: string): void {
    this.dataArray.splice(index, 0, data)
    this.notifyDataAdd(index)
  }

  public pushData(data: string): void {
    this.dataArray.push(data)
    this.notifyDataAdd(this.dataArray.length - 1)
  }
T
tianyu 已提交
236 237 238 239 240
}

@Entry
@Component
struct MyComponent {
L
luoying_ace_admin 已提交
241 242 243 244 245 246 247 248 249 250
  private data: MyDataSource = new MyDataSource()

  build() {
    List({ space: 3 }) {
      LazyForEach(this.data, (item: string) => {
        ListItem() {
          Row() {
            Image(item).width(50).height(50)
            Text(item).fontSize(20).margin({ left: 10 })
          }.margin({ left: 10, right: 10 })
T
tianyu 已提交
251
        }
L
luoying_ace_admin 已提交
252 253
        .onClick(() => {
          // 每点击一次列表项,数据增加一项
Y
yamila 已提交
254
          this.data.pushData('/path/image' + this.data.totalCount() + '.png')
L
luoying_ace_admin 已提交
255 256
        })
      }, item => item)
T
tianyu 已提交
257
    }
L
luoying_ace_admin 已提交
258
  }
T
tianyu 已提交
259 260 261 262 263
}
```

> **说明:**
>
L
luoying_ace_admin 已提交
264
> - LazyForEach必须在容器组件内使用,目前仅有List、Grid以及Swiper组件支持数据懒加载(即只加载可视部分以及其前后少量数据用于缓冲),其他组件仍然是一次性加载所有的数据。
T
tianyu 已提交
265
>
L
luoying_ace_admin 已提交
266
> - LazyForEach在每次迭代中,必须创建且只允许创建一个子组件。
T
tianyu 已提交
267
>
L
luoying_ace_admin 已提交
268
> - 生成的子组件必须是允许包含在LazyForEach父容器组件中的子组件。
T
tianyu 已提交
269
>
L
luoying_ace_admin 已提交
270
> - 允许LazyForEach包含在if/else条件渲染语句中,但不允许LazyForEach中出现if/else条件渲染语句。
T
tianyu 已提交
271
>
L
luoying_ace_admin 已提交
272
> - 为了高性能渲染,通过DataChangeListener对象的onDataChange方法来更新UI时,仅当itemGenerator中创建的子组件内使用了状态变量时,才会触发组件刷新。
T
tianyu 已提交
273
>
L
luoying_ace_admin 已提交
274
> - itemGenerator函数的调用顺序不一定和数据源中的数据项相同,在开发过程中不要假设itemGenerator和keyGenerator函数是否执行及其执行顺序。例如,以下示例可能无法正常工作:
T
tianyu 已提交
275 276 277
>
>   ```ts
>   LazyForEach(dataSource, 
L
luoying_ace_admin 已提交
278 279
>     item => Text(`${item.i}. item.data.label`)),
>     item => item.data.id.toString())
Y
yamila 已提交
280 281 282
>   ```

![lazyForEach](figures/lazyForEach.gif)