【HarmonyOS】State management and page routing jump implementation under decorator

        Starting from today, the blogger will open a new column to explain the popular technology "Hongmeng Development" on the market. For those who are new to this technology, it is necessary to understand Hongmeng first before learning Hongmeng Development. From you From the perspective of Hongmeng, what do you think it is? What is the significance of its appearance? Is Hongmeng just a mobile operating system? Can its emergence separate the world from Android and IOS? Can its future potential dominate the entire mobile phone market?

With such questions and curiosity about Hongmeng development, let us start mastering ArkUI state management today!

Table of contents

ArkUI state management

@State decorator

@Provide和@Consume

Page routing


ArkUI state management

In declarative UI, state is used to drive view updates, and the core concepts are state and view . The so-called state refers to the data that drives the view to update, or the variables defined in our custom components and marked by decorators; the so-called view refers to the user interface rendered by the GUI description; after the view is rendered, the user can The page elements in the view interact, and the value of the state variable is changed through interactive events such as click, touch, and drag. There is a mechanism inside arkui to monitor the value of the state variable. Once it is found that it has changed, it will be triggered. Re-rendering of the view. So this mechanism of interaction between state and view is called state management mechanism.

State management requires the use of many different decorators. Next, we start to learn the basic concepts of state management and the basic usage and precautions of the following decorators.

@State decorator

There are the following considerations for using the @State decorator:

1) Variables marked with the @State decorator must be initialized and cannot be empty.

2) @State supports Object, class, string, number, boolean, enum types and arrays of these types

3) Nested types (a property in Object is also an Object) and object properties in arrays cannot trigger view updates. The following is the demo code:

class Person {
  name: string
  age: number

  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }
}
@Entry
@Component
struct StatePage {
  idx: number = 1
  @State p: Person[] = [
    new Person('张三', 20)
  ]

  build(){
    Column(){
      Button('添加')
        .onClick(()=>{
          this.p.push(new Person('张三'+this.idx++, 20 ))
        })
      ForEach(
        this.p,
        (p, index) => {
          Row(){
            Text(`${p.name}: ${p.age}`)
              .fontSize(30)
              .onClick(() => {
                //数组内的元素变更不会触发数组的重新渲染
                // p.age++
                //数组重新添加、删除或者赋值的时候才会触发数组的重新渲染
                this.p[index] = new Person(p.name, p.age+1)
              })
            Button('删除')
              .onClick(()=>{
                this.p.splice(index, 1)
              })
          }
          .width('100%')
          .justifyContent(FlexAlign.SpaceAround)
        }
      )
    }
    .width('100%')
    .height('100%')
  }
}

The two decorators Prop and Link are used when synchronizing data between parent and child components. The following is the usage of both:

Decorator @Prop @Link
Sync type One-way sync Two-way sync
Variable types that are allowed to be decorated

1) @Prop only supports string, number, boolean, and enum types

2) The parent component is an object type, and the child component is an object property.

3) It cannot be an array or any

1) The parent and child types are consistent: string, number, boolean, enum, object, class, and their arrays

2) Adding, deleting, and replacing elements in the array will cause refresh

3) Nested types and object properties in arrays cannot trigger view updates

Next, use Prop and Link to complete a small case:

We pass the value to the child component through prop in the parent component, and the child component renders the page after receiving the value through the @Prop decorator. Here we use the stacking container and progress bar components provided by ArkUI to implement the page configuration:

// 统一卡片样式
@Styles function card(){
  .width('95%')
  .padding(20)
  .backgroundColor(Color.White)
  .borderRadius(15)
  .shadow({radius: 6, color: '#1F000000', offsetX: 2, offsetY: 4})
}

@Component
export struct TaskStatistics {
  @Prop totalTask: number // 总任务数量
  @Prop finishTask: number // 已完成任务数量
  build() {
    // 任务进度卡片
    Row(){
      Text('任务进度')
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
      // 堆叠容器,组件之间可以相互叠加显示
      Stack(){
        // 环形进度条
        Progress({
          value: this.finishTask,
          total: this.totalTask,
          type: ProgressType.Ring // 选择环形进度条
        })
          .width(100)
        Row(){
          Text(this.finishTask.toString())
            .fontColor('#36D')
            .fontSize(24)
          Text(' / ' + this.totalTask.toString())
            .fontSize(24)
        }
      }
    }
    .margin({top: 20, bottom: 10})
    .justifyContent(FlexAlign.SpaceEvenly)
    .card()

  }
}

If the child component modifies the value of the parent component, it needs to be implemented through the @Link decorator, and the parent component needs to get the value through $:

// 任务类
class Task {
  static id: number = 1 // 静态变量,内部共享
  name: string = `任务${Task.id++}` // 任务名称
  finished: boolean = false // 任务状态,是否已完成
}

// 统一卡片样式
@Styles function card(){
  .width('95%')
  .padding(20)
  .backgroundColor(Color.White)
  .borderRadius(15)
  .shadow({radius: 6, color: '#1F000000', offsetX: 2, offsetY: 4})
}

@Component
export struct TaskList {
  @Link totalTask: number // 总任务数量
  @Link finishTask: number // 已完成任务数量
  @State tasks: Task[] = [] // 任务数组
  // 任务更新触发函数
  handleTaskChange(){
    this.totalTask = this.tasks.length // 更新任务总数量
    this.finishTask = this.tasks.filter(item => item.finished).length // 更新任务数量
  }
  build() {
    Column(){
      // 任务新增按钮
      Button('新增任务')
        .width(200)
        .onClick(()=>{
          this.tasks.push(new Task()) // 新增任务数组
          this.handleTaskChange()
        })

      // 任务列表
      List({space: 10}){
        ForEach(
          this.tasks,
          (item: Task, index)=>{
            ListItem(){
              Row(){
                Text(item.name)
                  .fontSize(20)
                Checkbox()
                  .select(item.finished)
                  .onChange(val => {
                    item.finished = val // 更新当前的任务状态
                    this.handleTaskChange()
                  })
              }
              .card()
              .justifyContent(FlexAlign.SpaceBetween)
            }
            .swipeAction({end: this.DeleteButton(index)})
          }
        )
      }
      .width('100%')
      .layoutWeight(1)
      .alignListItem(ListItemAlign.Center)
    }
  }
  @Builder DeleteButton(index: number){
    Button(){
      Image($r('app.media.delete'))
        .fillColor(Color.Red)
        .width(20)
    }
    .width(40)
    .height(40)
    .type(ButtonType.Circle)
    .backgroundColor(Color.Red)
    .margin(5)
    .onClick(()=>{
      this.tasks.splice(index, 1)
      this.handleTaskChange()
    })
  }
}

Next, you need to reference these two sub-components in the parent component, and then pass parameters to obtain and transfer related values:

// 任务类
class Task {
  static id: number = 1 // 静态变量,内部共享
  name: string = `任务${Task.id++}` // 任务名称
  finished: boolean = false // 任务状态,是否已完成
}

import { TaskStatistics } from '../components/TaskStatistics'
import { TaskList } from '../components/TaskList'
@Entry
@Component
struct PropPage {
  @State totalTask: number = 0 // 总任务数量
  @State finishTask: number = 0 // 已完成任务数量
  @State tasks: Task[] = [] // 任务数组
  build(){
    Column({space: 10}){
      // 任务进度卡片
      TaskStatistics({ totalTask: this.totalTask, finishTask: this.finishTask })
      // 任务列表
      TaskList({ totalTask: $totalTask, finishTask: $finishTask })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F1F2F3')
  }
}

The final result is as follows:

@Provide和@Consume

These two decorators can provide two-way synchronization across components like @State and @Link. The operation method is very simple. Use the Provide decorator between parent components, and use the Consume decorator for all child components. The parent component does not need to pass parameters. Just call the subcomponent function directly:

The final result is as follows:

Although it is relatively simpler than Prop and Link, using Provide and Consume still comes at a cost. Originally, parameters need to be passed, but using Provide does not require passing parameters. It automatically helps us maintain it internally. There must be some resources. It’s a waste, so we should use Prop as much as possible if we can. If we really can’t use it, we can consider Provide.

These two decorators are used for bidirectional data synchronization in scenarios involving nested objects or array elements as objects:

We add a style attribute to the text in the task list. When we click to check, the text will turn gray and add an underline style:

But when we check it, the view does not change. The reason is that our Task is an object type and the elements of the array are objects. Modification of the properties of the object will not trigger the re-rendering of the view, so here we need to use this The decorator explained this time can be solved:

We set the @Observed decorator on the class object:

Then set the @ObjectLink decorator at the location where you want to modify the object attribute value. Because a task list here is traversed through ForEach, we need to separate this location to form a function, and then set the @ObjectLink decorator on the item to be used. processor, because we still need to call a function, but the function of the task list cannot be moved, so we also pass the called function as a parameter:

@Component
struct TaskItem {
  @ObjectLink item: Task
  onTaskChange: () => void
  build(){
    Row(){
      if (this.item.finished){
        Text(this.item.name)
          .finishedTask()
      }else{
        Text(this.item.name)
      }
      Checkbox()
        .select(this.item.finished)
        .onChange(val => {
          this.item.finished = val // 更新当前的任务状态
          this.onTaskChange()
        })
    }
    .card()
    .justifyContent(FlexAlign.SpaceBetween)
  }
}

In order to ensure that this pointer has not changed during the transfer process, when we pass the function, we also need to specify this pointer through the bind function:

The final result is as follows:

Page routing

Page routing refers to the implementation of jumps and data transfer between different pages in the application. If you have studied the front-end vue or react framework, you can easily understand the concept of page routing jumps. The following is carried out in Hongmeng development The API functions called by page routing jumps and the functions of the corresponding functions are very similar to the front-end vue framework:

Router has two page jump modes , which are:

router.pushUrl() : The target page will not replace the current page, but will be pushed into the page stack, so router.back() can be used to return the current page.

router.replaceUrl() : The target page replaces the current page. The current page will be destroyed and resources released, and the current page cannot be returned.

Router has two page instance modes , which are:

Standard : Standard instance mode. Each jump will create a new target page and push it to the top of the stack. This mode is the default

Single : Single instance mode. If the target page is already in the stack, the page with the same URL closest to the top of the stack will be moved to the top of the stack and reloaded.

After understanding the basic concepts of page routing, let's start to introduce how to use page routing in the case:

First we define routing information on the index homepage:

// 定义路由信息
class RouterInfo {
  url: string // 页面路径
  title: string // 页面标题
  constructor(url: string, title: string) {
    this.url = url
    this.title = title
  }
}

Next, define routing-related information and the static style of the page in the struct structure:

  @State message: string = '页面列表'
  private routers: RouterInfo[] = [
    new RouterInfo('pages/router/test1', '页面1'),
    new RouterInfo('pages/router/test2', '页面2'),
    new RouterInfo('pages/router/test3', '页面3'),
    new RouterInfo('pages/router/test4', '页面4')
  ]

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .fontColor('#008c8c')
          .height(80)

        List({space: 15}){
          ForEach(
            this.routers,
            (router, index) => {
              ListItem(){
                this.RouterItem(router, index + 1)
              }
            }
          )
        }
        .layoutWeight(1)
        .alignListItem(ListItemAlign.Center)
        .width('100%')
      }
      .width('100%')
      .height('100%')
    }
  }

Define the RouterItem function and set the click function for routing jump:

@Builder RouterItem(r: RouterInfo, i: number){
    Row(){
      Text(i+'.')
        .fontSize(20)
        .fontColor(Color.White)
      Blank()
      Text(r.title)
        .fontSize(20)
        .fontColor(Color.White)
    }
    .width('90%')
    .padding(12)
    .backgroundColor('#38f')
    .shadow({radius: 6, color: '#4f0000', offsetX: 2, offsetY: 4})
    .onClick(()=>{
      // router跳转,传递3个参数
      router.pushUrl(
        // 跳转路径及参数
        {
          url: r.url,
          params: {id: i}
        },
        // 页面实例
        router.RouterMode.Single,
        // 跳转失败的一个回调
        err => {
          if (err) {
            console.log(`跳转失败,errCode:${err.code} errMsg: ${err.message}`)
          }
        }
      )
    })
  }

Define 3 route jump pages, and set the fourth route to have no jump page for comparison:

Note that if you are just creating a new ArkTS page, you need to configure routing in the following files:

If you find it annoying to create a routing path every time you create a page, you can use the following creation method, which will automatically configure the routing path for us without having to set it manually:

In the subcomponent, if we want to get the passed parameters, we can call the getParams function, and then call the back function:

If you want to add a returned warning, you can use the following method:

The final result is:

Guess you like

Origin blog.csdn.net/qq_53123067/article/details/135320571
Recommended