Use of ArkTS @Observed, @ObjectLink state decorators

 effect

The @Observed and @ObjectLink decorators are used for bidirectional data synchronization in scenarios involving nested objects or array elements as objects.

Use of status

1. Nested objects

We set the parent class to the @Observed state. At this time, the child should set @ObjectLink to complete the two-way binding of data, so we build a component and let the state become a variable to complete the state of the attribute.

@Observed
class Person{
  name: string
  age: number
  gf: Person

  constructor(name:string, age: number, gf?: Person) {
    this.name = name
    this.age = age
    this.gf = gf
  }
}

@Entry
@Component
struct Parent {
  @State p: Person = new Person('zhangsan',21,new Person('lisi',18))
  build() {
    Row() {
      Column() {
        Child({p:this.p.gf})
          .onClick(()=>this.p.gf.age++)
      }
      .width('100%')
    }
    .height('100%')
  }
}

@Component
struct Child{
  @ObjectLink p: Person
  build(){
    Column(){
      Text(`${this.p.name} : ${this.p.age}`)
    }
  }
}

2. Object elements in the array

As long as our data passes through the hands of Child, that is, using the variables bound by @ObjectLink, the update of the variable status will directly trigger the update of the view.

@Observed
class Person{
  name: string
  age: number
  gf: Person

  constructor(name:string, age: number, gf?: Person) {
    this.name = name
    this.age = age
    this.gf = gf
  }
}

@Entry
@Component
struct Parent {
  @State p: Person = new Person('zhangsan',21,new Person('lisi',18))
  @State gfs: Person[] = [
  new Person('wangwu',18),new Person('yangliu',19), ]
  build() {
    Row() {
      Column() {
        Child({p:this.p.gf})
          .onClick(()=>this.p.gf.age++)
        Text('===== 列表 =====')
        ForEach(
          this.gfs,
          p=> {
            Child({p:p}).onClick(()=>p.age++)
          }
        )
      }
      .width('100%')
    }
    .height('100%')
  }
}

@Component
struct Child{
  @ObjectLink p: Person
  build(){
    Column(){
      Text(`${this.p.name} : ${this.p.age}`)
    }
  }
}

 Task statistics case verification

Let's use these two states in the task statistics case.

We now want to achieve an effect, which is to turn the completed tasks into gray and add a strikethrough effect.

// 任务完成样式
@Extend(Text) function finishedTask(){
  .decoration({type:TextDecorationType.LineThrough})
  .fontColor('#b1b2b1')
}
  
Row(){
   if(item.finished){
       Text(item.name)
       .finishedTask()
        }else{
       Text(item.name)
        }
      Checkbox()
      .select(item.finished)
      .onChange(val=>{
     item.finished = val
    // 通过过滤方法 更新已完成的任务数量
      this.handleTaskChange()
    })
}

We judged the status of each task through if to decide which effect to use, and found that our effect could not be achieved. Because foreach traverses the array, it is equivalent to changing the properties of the array elements. It happens that the property we want to modify is an object, so our modification will not trigger the update of the view, so we should use the @Observed and @ObjectLink states to complete it. This case. 

So let's add state to the object of the nested array that needs to be implemented.

We add the @Observed state to the original class Task, and add the @ObjectLink state to the item in the Row generated by the encapsulation loop. Since there is no state-changing function in our component, we remove it first and complete the style modification first.



@Observed
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: '#1f0000',offsetX:2,offsetY:4})
}

// 任务完成样式
@Extend(Text) function finishedTask(){
  .decoration({type:TextDecorationType.LineThrough})
  .fontColor('#b1b2b1')
}


// 任务统计信息
class StaticInfo{
  totalTask: number = 0
  finishTask: number = 0
}

@Entry
@Component
struct PropPage {
  // 统计信息
  @Provide stat: StaticInfo = new StaticInfo()

  build() {
      Column({space:10}) {
        // 1.任务进度卡片
        TaskStatistics()
        // 2.任务列表
        TaskList();
      }
      .width('100%')
      .height('100%')
      .backgroundColor('#f1f2f3')
  }

}


@Component
struct TaskStatistics {

  @Consume stat: StaticInfo

  build(){
    Row(){
      Text("任务进度")
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
      Stack(){
        Progress({
          value:this.stat.finishTask,
          total:this.stat.totalTask,
          type:ProgressType.Ring
        })
          .width(100)
        Row(){
          Text(this.stat.finishTask.toString())
            .fontSize(24)
            .fontColor('#36d')
          Text(' / ' +this.stat.totalTask.toString())
            .fontSize(24)
        }
      }
    }
    .card()
    .margin({top:20,bottom:10})
    .justifyContent(FlexAlign.SpaceEvenly)
  }
}

@Component
struct TaskList {
  // 任务数量
  @State tasks: Task[] = []
  @Consume stat: StaticInfo
  // 通过过滤方法 更新已完成的任务数量
  handleTaskChange(){
    // 更新任务总数量
    this.stat.totalTask = this.tasks.length
    // 已经完成的任务数量
    this.stat.finishTask = this.tasks.filter(item => item.finished).length
  }
  build(){
      Column(){
        // 2.新增任务按钮
        Button('新增任务')
          .width(200)
          .onClick(()=>{
            // 1.新增任务
            this.tasks.push(new Task())
            // 2.更新任务数组
            this.stat.totalTask = this.tasks.length
          })
        // 3.卡片列表
        List({space:10}){
          ForEach(
            this.tasks,
            (item: Task,index)=>{
              ListItem(){
                TaskItem({item:item})
              }
              .swipeAction({end:this.DeleteButton(index)})
            }
          )
        }
        .width('100%')
        .layoutWeight(1)
        .alignListItem(ListItemAlign.Center)
      }
  }
  @Builder DeleteButton(index: number){
    Button("删除")
      .onClick(()=>{
        this.tasks.splice(index,1)
        this.handleTaskChange()
      })
  }
  }

@Component
struct TaskItem {

  @ObjectLink item: Task
  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.handleTaskChange()
        })
    }
    .card()
    .justifyContent(FlexAlign.SpaceBetween)
  }
}

We will find that by clicking on our Done check, our color and strikethrough status are successfully reflected, so our nested array two-way binding is successful.

Now let's complete the functionality and update our completion status.

method passing

We can make methods public through method passing.

 

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

We write a function in the taskItem method and pass the function in when calling. This is equivalent to us calling this method in taskItem. 

Then we will find that it is still not implemented. Because the pointing of this in the child component is incorrect.

When the method is passed, the point of this needs to be confirmed because the person who called the method has changed.

So we need to ensure that when passing methods, the pointer of this cannot be changed. At this time we need to use an API

bind(this)

At this time we can fully realize the effect. 

Guess you like

Origin blog.csdn.net/a_strong_pig/article/details/135108012