DEV Community

ZHZL-m
ZHZL-m

Posted on

【Journey of HarmonyOS Next】ArkTS Syntax (3) -> Rendering Control

Image description

ArkTS also provides the ability to control rendering. Conditional rendering can render the UI content in the corresponding state according to the different states of the application. Circular rendering iteratively fetches data from a data source and creates the appropriate components during each iteration.

1 -> Conditional rendering

Use if/else for conditional rendering.

illustrate

if/else conditional statements can use state variables.

Use if/else to make the rendering of a child component dependent on a conditional statement.

Must be used within the container assembly.

Some container components limit the type or number of subcomponents, and when you use if/else in those components, these restrictions will also apply to the components created in the if/else statement. For example, if only the GridItem component is supported by the child components of the Grid container component, and if if/else is used in the Grid, only the GridItem component is allowed in the if/else statement.

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)
  }
}
Enter fullscreen mode Exit fullscreen mode

2 -> Render in a loop

Fetch data from an array with forEach looping and creating a component for each data item reduces code complexity.

ForEach(
  arr: any[], 
  itemGenerator: (item: any, index?: number) => void,
  keyGenerator?: (item: any, index?: number) => string 
)
Enter fullscreen mode Exit fullscreen mode

Image description

illustrate

ForEach must be used within the container component.

The generated child component should be one that is allowed to be included in the ForEach parent container component.

Allows if/else conditional rendering to be included in the child component generator function.

// test.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())
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Image description

3 -> Data lazy loading

LazyForEach allows you to iterate on data on-demand from the provided data sources and create the appropriate components during each iteration.

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;          
}
Enter fullscreen mode Exit fullscreen mode

Image description

3.1 -> IDataSource type description

Image description

3.2 -> DataChangeListener类型说明

Image description

// test2.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)
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

illustrate

LazyForEach must be used in the container component, currently only the List, Grid, and Swiper components support lazy loading of data (i.e., only the visible part and a small amount of data before and after it are loaded for buffering), and the other components still load all the data at once.

LazyForEach must be created and only one child component is allowed to be created in each iteration.

The generated child component must be a child component that is allowed to be included in the LazyForEach parent container component.

LazyForEach is allowed to be included in if/else conditional rendering statements, but not if/else conditional rendering statements are allowed to appear in LazyForEach.

For high-performance rendering, when updating the UI via the onDataChange method of the DataChangeListener object, a component refresh is triggered only if a state variable is used in the child component created in the itemGenerator.

The order in which the itemGenerator function is called is not necessarily the same as that of the data items in the data source, so do not assume whether the itemGenerator and keyGenerator functions are executed and in the order in which they are executed. For example, the following example might not work:

LazyForEach(dataSource,
item => Text('${item.i}. item.data.label')),
item => item.data.id.toString())
Enter fullscreen mode Exit fullscreen mode

Image description

Top comments (0)