MiniX自绘渲染跨平台框架
  • 框架说明
  • 声明式开发范式
  • 内置Api
指南
接口
  • Minix CLI
示例
  • 类Web框架

    • 框架说明
    • 类Web开发范式
    • 内置Api
  • 指南
  • 组件
  • 接口
  • 示例
  • 规范
  • DophinHybrid

    • 快速上手 (opens new window)
    • UI 组件库 (opens new window)
    • jsBridge 接口 (opens new window)
  • DolphinWeex

    • 快速上手 (opens new window)
    • UI 组件库 (opens new window)
    • jsBridge 接口 (opens new window)
  • 发布消息
  • 常见问题
  • 更新日志
  • 框架说明
  • 声明式开发范式
  • 内置Api
指南
接口
  • Minix CLI
示例
  • 类Web框架

    • 框架说明
    • 类Web开发范式
    • 内置Api
  • 指南
  • 组件
  • 接口
  • 示例
  • 规范
  • DophinHybrid

    • 快速上手 (opens new window)
    • UI 组件库 (opens new window)
    • jsBridge 接口 (opens new window)
  • DolphinWeex

    • 快速上手 (opens new window)
    • UI 组件库 (opens new window)
    • jsBridge 接口 (opens new window)
  • 发布消息
  • 常见问题
  • 更新日志
  • 框架说明

    • 概述
    • 声明式 UI 语法

      • 基本 UI 描述
      • 状态管理

      • 动态构建 UI 元素
      • 渲染控制
        • 条件渲染
        • 循环渲染
        • 示例
        • 数据懒加载
        • 示例
      • 使用限制与扩展
    • 深入理解组件化

    • 应用生命周期
    • 资源管理

    • 像素单位
    • 应用包结构配置文件的说明
  • 常见布局开发指导

  • 性能提升的推荐方法

# 渲染控制

声明式 TS 也提供了渲染控制的能力。条件渲染可根据应用的不同状态,渲染对应状态下的 UI 内容。循环渲染可从数据源中迭代获取数据,并在每次迭代过程中创建相应的组件。

# 条件渲染

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

说明:

  • if/else 条件语句可以使用状态变量。

  • 使用 if/else 可以使子组件的渲染依赖条件语句。

  • 必须在容器组件内使用。

  • 某些容器组件限制子组件的类型或数量,将 if/else 用于这些组件内时,这些限制将同样应用于 if/else 语句内创建的组件。例如,Grid 容器组件的子组件仅支持 GridItem 组件,在 Grid 内使用 if/else 时,则 if/else 语句内也仅允许使用 GridItem 组件。

Column() {
  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)
  }
}
1
2
3
4
5
6
7
8
9

# 循环渲染

通过循环渲染(ForEach)从数组中获取数据,并为每个数据项创建相应的组件,可减少代码复杂度。

ForEach(
  arr: any[],
  itemGenerator: (item: any, index?: number) => void,
  keyGenerator?: (item: any, index?: number) => string
)
1
2
3
4
5

参数:

参数名 参数类型 必填 参数描述
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 中的所有节点都将重建。

说明:

  • ForEach 必须在容器组件内使用。

  • 生成的子组件应当是允许包含在 ForEach 父容器组件中的子组件。

  • 允许子组件生成器函数中包含 if/else 条件渲染,同时也允许 ForEach 包含在 if/else 条件渲染语句中。

  • itemGenerator 函数的调用顺序不一定和数组中的数据项相同,在开发过程中不要假设 itemGenerator 和 keyGenerator 函数是否执行及其执行顺序。例如,以下示例可能无法正常工作:

    ForEach(
      anArray.map((item1, index1) => {
        return { i: index1 + 1, data: item1 };
      }),
      (item) => Text(`${item.i}. item.data.label`),
      (item) => item.data.id.toString()
    );
    
    1
    2
    3
    4
    5
    6
    7

# 示例

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

  build() {
    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())
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

forEach1

# 数据懒加载

通过数据懒加载(LazyForEach)从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。

LazyForEach(
  dataSource: IDataSource,
  itemGenerator: (item: any) => void,
  keyGenerator?: (item: any) => string
): void

interface IDataSource {
  totalCount(): number;
  getData(index: number): any;
  registerDataChangeListener(listener: DataChangeListener): void;
  unregisterDataChangeListener(listener: DataChangeListener): void;
}

interface DataChangeListener {
  onDataReloaded(): void;
  onDataAdd(index: number): void;
  onDataMove(from: number, to: number): void;
  onDataDelete(index: number): void;
  onDataChange(index: number): void;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

参数:

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

# IDataSource 类型说明

名称 描述
totalCount(): number 获取数据总数。
getData(index: number): any 获取索引值 index 对应的数据。
registerDataChangeListener(listener:DataChangeListener): void 注册数据改变的监听器。
unregisterDataChangeListener(listener:DataChangeListener): void 注销数据改变的监听器。

# DataChangeListener 类型说明

名称 描述
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 的位置有数据变化。

# 示例

// xxx.ets
class BasicDataSource implements IDataSource {
  private listeners: DataChangeListener[] = []

  public totalCount(): number {
    return 0
  }

  public getData(index: number): any {
    return undefined
  }

  registerDataChangeListener(listener: DataChangeListener): void {
    if (this.listeners.indexOf(listener) < 0) {
      console.info('add listener')
      this.listeners.push(listener)
    }
  }

  unregisterDataChangeListener(listener: DataChangeListener): void {
    const pos = this.listeners.indexOf(listener);
    if (pos >= 0) {
      console.info('remove listener')
      this.listeners.splice(pos, 1)
    }
  }

  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)
    })
  }
}

class MyDataSource extends BasicDataSource {
  // 初始化数据列表
  private dataArray: string[] = ['/path/image0.png', '/path/image1.png', '/path/image2.png', '/path/image3.png']

  public totalCount(): number {
    return this.dataArray.length
  }

  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)
  }
}

@Entry
@Component
struct MyComponent {
  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 })
        }
        .onClick(() => {
          // 每点击一次列表项,数据增加一项
          this.data.pushData('/path/image' + this.data.totalCount() + '.png')
        })
      }, item => item)
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103

说明:

  • LazyForEach 必须在容器组件内使用,目前仅有 List、Grid 以及 Swiper 组件支持数据懒加载(即只加载可视部分以及其前后少量数据用于缓冲),其他组件仍然是一次性加载所有的数据。

  • LazyForEach 在每次迭代中,必须创建且只允许创建一个子组件。

  • 生成的子组件必须是允许包含在 LazyForEach 父容器组件中的子组件。

  • 允许 LazyForEach 包含在 if/else 条件渲染语句中,但不允许 LazyForEach 中出现 if/else 条件渲染语句。

  • 为了高性能渲染,通过 DataChangeListener 对象的 onDataChange 方法来更新 UI 时,仅当 itemGenerator 中创建的子组件内使用了状态变量时,才会触发组件刷新。

  • itemGenerator 函数的调用顺序不一定和数据源中的数据项相同,在开发过程中不要假设 itemGenerator 和 keyGenerator 函数是否执行及其执行顺序。例如,以下示例可能无法正常工作:

    LazyForEach(dataSource,
      item => Text(`${item.i}. item.data.label`)),
      item => item.data.id.toString())
    
    1
    2
    3

lazyForEach