鸿蒙NEXT开发-组件事件监听和状态管理

123 阅读3分钟

组件事件

监听原生组件的事件和设置属性的方式是一样的都是链式调用,值得注意的是,我们注册事件必须使用箭头函数的写法,Next版本禁止使用匿名函数的形式来给组件注册事件

  • 匿名函数 function () {}
  • 尝试给一个TextInput和一个按钮注册一个值改变事件和点击事件
@Entry
  @Component
  struct Index {

    build() {
      Row() {
        Column({ space: 15 }) {
          Row() {
            TextInput({ placeholder: '请输入用户名' })
              .backgroundColor('#f4f5f6')
              .width('100%').onChange((value) => {
                console.log(value)
              })
          }.padding({
            left: 20,
            right: 20
          })

          Row() {
            Button("登录")
              .width('100%')
              .onClick(() => {
                AlertDialog.show({
                  message: '登录成功'
                })
              })

          }.padding({
            left: 20,
            right: 20
          })

        }
        .width('100%')
      }
      .height('100%')
    }
  }
  • promptAction弹出需要引入一个包才可以使用的,功能更多一些
  • AlertDialog 不需要引入包使用的,功能单一

请注意:在注册事件中的逻辑必须使用箭头函数 () => {}

  1. 因为function中this指向为undefind
  2. 箭头函数中的this指向当前struct实例,可以方便的调用方法和获取属性

给前几节课做的登录小案例加上事件

import { promptAction } from '@kit.ArkUI'

@Entry
  @Component
  struct Index {
    /**
   * 手机号
   */
    @State phone:string=''

    /**
   * 验证码
   */
    @State code:string=''

    /**
    * 获取验证码
    */
    getCode(){
      // 判断手机号是否存在
      if(this.phone===''){
        promptAction.showToast({
          message:'手机号不能为空'
        })
        return
      }
      promptAction.showToast({
        message:'获取短信验证码成功,当前验证码是1234'
      })
    }

    /**
   * 登录
   */
    login(){
      if(this.phone!='' && this.code==='1234'){
        promptAction.showToast({message:'登录成功'})
        return
      }
      promptAction.showToast({message:'登录失败'})
      // 恢复手机号跟验证码初始化的值
      this.phone=''
      this.code=''
    }

    build() {
      Column() {
        // 华为账号登录
        Column({space:20}){
          TextInput({placeholder:'请输入手机号',text:this.phone})
            .width(300)
            .borderRadius(2)
            .onChange((value)=>{
              this.phone=value
            })
          TextInput({placeholder:'请输入验证码',text:this.code})
            .type(InputType.Password)
            .width(300)
            .borderRadius(2)
            .onChange((value)=>{
              this.code=value
            })

          Text('短信验证码登录').fontColor(Color.Blue).align(Alignment.Start).width('80%')
            .margin({bottom:10})
            .onClick(()=>{
              this.getCode()
            })

        }

        Button('登录')
          .width(300)
          .type(ButtonType.Normal)
          .borderRadius(10)
          .backgroundColor(Color.Red)
          .onClick(()=>{
            this.login()
          })
      }.justifyContent(FlexAlign.Center).width('100%').height('100%')
    }
  }

组件状态管理

在声明式UI编程框架中,UI是程序状态的运行结果,用户构建了一个UI模型,其中应用的运行时的状态是参数。当参数改变时,UI作为返回结果,也将进行对应的改变。这些运行时的状态变化所带来的UI的重新渲染,在ArkUI中统称为状态管理机制。

@State装饰器:组件内状态

@State装饰的变量,或称为状态变量,一旦变量拥有了状态属性,就和自定义组件的渲染绑定起来。当状态改变时,UI会发生对应的渲染改变。

需要注意的是,State修饰的类型

Object、class、string、number、boolean、enum类型,以及这些类型的数组。嵌套类型以及数组中的对象属性无法触发视图更新

类型必须被指定。

不支持any,不支持简单类型和复杂类型的联合类型,不允许使用undefined和null。

@Entry
@Component
struct Index {
   @State message:string='hello world'
  build() {
    Column(){
      Text(this.message).width(100).height(50).onClick(()=>{
        this.message='hello state'
      })

    }.width('100%').height('100%')
  }
}
@Entry
@Component
struct Index {
   @State name:string='东林'
   @State age:number=18
  build() {
    Column(){
      Text(`${this.name}:${this.age}`)
        .width(100)
        .height(50)
        .onClick(()=>{
        this.age++
      })

    }.width('100%').height('100%')
  }
}
class People{
  name:string
  age:number

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

@Entry
  @Component
  struct Index {
    @State p:People=new People('东林',18)
    build() {
      Column(){
        Text(`${this.p.name}:${this.p.age}`)
          .width(100)
          .height(50)
          .onClick(()=>{
            this.p.age++
          })

      }.width('100%').height('100%')
    }
  }
class People {
  name: string
  age: number
  model:Model

  constructor(name: string, age: number,model:Model) {
    this.name = name
    this.age = age
    this.model=model
  }
}

class Model{
  like:string=''
  constructor(like:string) {
    this.like=like
  }
}

@Entry
  @Component
  struct Index {
    @State p: People = new People('东林', 18, new Model('篮球'))

    build() {
      Column() {
        Text(`${this.p.model.like}`)
          .width(100)
          .height(50)
          .onClick(() => {
            this.p.model.like = '足球'
          })

      }.width('100%').height('100%')
    }
  }

@Prop装饰器:父子单向同步

@Prop装饰的变量可以和父组件建立单向的同步关系。@Prop装饰的变量是可变的,但是变化不会同步回其父组件。

允许装饰的变量的类型

Object、class、string、number、boolean、enum类型,以及这些类型的数组。

不支持any,支持undefined和null。

支持Date类型。

嵌套类型以及数组中的对象属性无法触发视图更新

@Entry
@Component
struct Index {
  @State name: string = '父亲'
  @State age: number = 0

  build() {
    Column() {
      Text(`${this.name} : ${this.age}`)
        .width(100)
        .height(50)
        .onClick(() => {
          this.age = 18
        }).backgroundColor(Color.Green)
      Demo({ age: this.age })
    }.width('100%').height('100%')
  }
}


@Component
struct Demo {
  @Prop age: number = 0

  build() {
    Column() {
      Text(this.age.toString())
        .width(100)
        .height(50).backgroundColor(Color.Pink)
    }.width('100%').height('100%')

  }
}

@Link装饰器:父子双向同步

子组件中被@Link装饰的变量与其父组件中对应的数据源建立双向数据绑定。

允许装饰的变量类型

Object、class、string、number、boolean、enum类型,以及这些类型的数组。

支持Date类型。嵌套类型以及数组中的对象属性无法触发视图更新

@Entry
  @Component
  struct Index {
    @State name: string = '父亲'
    @State age: number = 0

    build() {
      Column() {
        Text(`${this.name} : ${this.age}`)
          .width(100)
          .height(50)
          .onClick(() => {
            this.age = 18
          }).backgroundColor(Color.Green)
        Demo({ age: this.age })
      }.width('100%').height('100%')
    }
  }


@Component
  struct Demo {
    @Link age: number

    build() {
      Column() {
        Text(this.age.toString())
          .width(100)
          .height(50).backgroundColor(Color.Pink).onClick(()=>{
            this.age=20
          })
      }.width('100%').height('100%')

    }
  }

@Provide和@Consume装饰器:与后代组件双向同步

@Provide和@Consume,应用于与后代组件的双向数据同步,应用于状态数据在多个层级之间传递的场景。

@Provide/@Consume装饰的状态变量有以下特性:

  • @Provide装饰的状态变量自动对其所有后代组件可用,即该变量被“provide”给他的后代组件。由此可见,@Provide的方便之处在于,开发者不需要多次在组件之间传递变量。
  • 后代通过使用@Consume去获取@Provide提供的变量,建立在@Provide和@Consume之间的双向数据同步,与@State/@Link不同的是,前者可以在多层级的父子组件之间传递。
  • @Provide和@Consume可以通过相同的变量名或者相同的变量别名绑定,建议类型相同,否则会发生类型隐式转换,从而导致应用行为异常。

允许装饰的变量类型

Object、class、string、number、boolean、enum类型,以及这些类型的数组。

支持Date类型。

嵌套类型以及数组中的对象属性无法触发视图更新

@Entry
  @Component
  struct Index {
    @State name: string = '父亲'
    @Provide age: number = 0

    build() {
      Column() {
        Text(`${this.name} : ${this.age}`)
          .width(100)
          .height(50)
          .onClick(() => {
            this.age = 18
          }).backgroundColor(Color.Green)
        Demo()
      }.width('100%').height('100%')
    }
  }


@Component
  struct Demo {
    @Consume age: number

    build() {
      Column() {
        Text(this.age.toString())
          .width(100)
          .height(50).backgroundColor(Color.Pink).onClick(()=>{
            this.age=20
          })
      }.width('100%').height('100%')

    }
  }

@Observed和@ObjectLink装饰器:嵌套类对象属性变化

上文所述的装饰器仅能观察到第一层的变化,但是在实际应用开发中,应用会根据开发需要,封装自己的数据模型。对于多层嵌套的情况,比如二维数组,或者数组项class,或者class的属性是class,他们的第二层的属性变化是无法观察到的。这就引出了@Observed/@ObjectLink装饰器。

@ObjectLink和@Observed类装饰器用于在涉及嵌套对象或数组的场景中进行双向数据同步:

  • 被@Observed装饰的类,可以被观察到属性的变化;
  • 子组件中@ObjectLink装饰器装饰的状态变量用于接收@Observed装饰的类的实例,和父组件中对应的状态变量建立双向数据绑定。这个实例可以是数组中的被@Observed装饰的项,或者是class object中的属性,这个属性同样也需要被@Observed装饰。
  • @Observed用于嵌套类场景中,观察对象类属性变化,要配合自定义组件使用(示例详见嵌套对象),如果要做数据双/单向同步,需要搭配@ObjectLink或者@Prop使用(示例详见@Prop与@ObjectLink的差异)。

允许装饰的变量类型

必须为被@Observed装饰的class实例,必须指定类型。

不支持简单类型,可以使用@Prop

支持继承Date、Array的class实例

@Observed
  class People {
    name: string
    age: number
    model: Model

    constructor(name: string, age: number, model: Model) {
      this.name = name
      this.age = age
      this.model = model
    }
  }

@Observed
  class Model {
    like: string = ''

    constructor(like: string) {
      this.like = like
    }
  }

@Entry
  @Component
  struct Index {
    @State p: People = new People('东林', 18, new Model('篮球'))

    build() {
      Column() {
        child({ m: this.p.model }).onClick(() => {
          this.p.model.like = '足球'
        })
      }.width('100%').height('100%')
    }
  }

@Component
  struct child {
    @ObjectLink m: Model

    build() {
      Column() {
        Text(`${this.m.like}`)
          .width(100)
          .height(50)

      }

    }
  }