写点什么

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

作者:东林知识库
  • 2025-03-31
    江苏
  • 本文字数:8195 字

    阅读完需:约 27 分钟

1. 组件事件

监听原生组件的事件和设置属性的方式是一样的都是链式调用,值得注意的是,我们注册事件必须使用箭头函数的写法,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 实例,可以方便的调用方法和获取属性

1.1 登录小案例

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%') } }
复制代码

2. 组件状态管理

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



编辑

2.1 @State 装饰器:组件内状态

2.1.1 基本介绍

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


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


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


类型必须被指定。


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

2.1.2 简单代码示例-字符串类型

@Entry@Componentstruct Index {   @State message:string='hello world'  build() {    Column(){      Text(this.message).width(100).height(50).onClick(()=>{        this.message='hello state'      })
}.width('100%').height('100%') }}
复制代码

2.1.3 简单代码示例-number 类型

@Entry@Componentstruct 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%') }}
复制代码

2.1.4 简单代码示例-对象类型

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%') } }
复制代码

2.1.5 简单代码示例-嵌套对象-不能触发视图更新

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%') } }
复制代码

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

2.2.1 基本介绍

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


允许装饰的变量的类型


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


不支持 any,支持 undefined 和 null。


支持 Date 类型。


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



编辑

2.2.2 简单代码示例

@Entry@Componentstruct 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%') }}

@Componentstruct Demo { @Prop age: number = 0
build() { Column() { Text(this.age.toString()) .width(100) .height(50).backgroundColor(Color.Pink) }.width('100%').height('100%')
}}
复制代码

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

2.3.1 基本介绍

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


允许装饰的变量类型


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


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



编辑

2.3.2 简单代码示例

@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%')
} }
复制代码

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

2.4.1 基本介绍

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


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


  • @Provide 装饰的状态变量自动对其所有后代组件可用,即该变量被“provide”给他的后代组件。由此可见,@Provide 的方便之处在于,开发者不需要多次在组件之间传递变量。

  • 后代通过使用 @Consume 去获取 @Provide 提供的变量,建立在 @Provide 和 @Consume 之间的双向数据同步,与 @State/@Link 不同的是,前者可以在多层级的父子组件之间传递。

  • @Provide 和 @Consume 可以通过相同的变量名或者相同的变量别名绑定,建议类型相同,否则会发生类型隐式转换,从而导致应用行为异常。


允许装饰的变量类型


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


支持 Date 类型。


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

2.4.2 简单代码示例

@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%')
} }
复制代码

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

2.5.1 基本介绍

上文所述的装饰器仅能观察到第一层的变化,但是在实际应用开发中,应用会根据开发需要,封装自己的数据模型。对于多层嵌套的情况,比如二维数组,或者数组项 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 实例

2.5.2 简单代码示例

@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)
}
} }
复制代码

3. 排行榜小案例

实现如下功能



编辑

3.1 代码结构


编辑

3.2 Index.ets

import TitleComponent from '../component/TitleComponent'import TableHeaderComponent from '../component/TableHeaderComponent'import ItemComponent from '../component/ItemComponent'import FruitModel from '../model/FruitModel'
@Entry@Componentstruct Index { @State fruitModels: FruitModel[] = [new FruitModel('1', '苹果', '10000') , new FruitModel('2', '橘子', '9000') , new FruitModel('3', '香蕉', '8000') , new FruitModel('4', '梨子', '7000') , new FruitModel('5', '枣子', '6000') , new FruitModel('6', '山竹', '5000') , new FruitModel('7', '桃子', '4000') , new FruitModel('8', '西瓜1', '3000') , new FruitModel('9', '西瓜2', '3000') , new FruitModel('10', '西瓜3', '3000') , new FruitModel('11', '西瓜4', '3000') , new FruitModel('12', '西瓜5', '3000') , new FruitModel('13', '西瓜6', '3000') , new FruitModel('14', '西瓜7', '3000') , new FruitModel('15', '西瓜8', '3000') , new FruitModel('16', '西瓜9', '3000') , new FruitModel('17', '西瓜10', '3000') , new FruitModel('18', '西瓜11', '3000') , new FruitModel('19', '西瓜12', '3000') , new FruitModel('20', '西瓜13', '3000') ]
/** * 刷新数据 */ load() {
}
build() { Column() { // 标题 TitleComponent({ fruitModels: this.fruitModels }) // 表头 TableHeaderComponent().margin({ bottom: 20 }) // 内容 ItemComponent({ fruitModels: this.fruitModels }) } .height('100%') .width('100%') }}
复制代码

3.3 FruitModel.ets

/** * 水果数据类 */export default class FruitModel {  /**   * 排名   */  id: string  /**   * 水果名   */  name: string  /**   * 投票数   */  vote: string
constructor(id: string, name: string, vote: string) { this.id = id this.name = name this.vote = vote
}}
复制代码

3.4 TitleComponent.ets

import AppContext from '@ohos.app.ability.common'import FruitModel from '../model/FruitModel'
/** * 水果排行榜标题组件 */@Component export default struct TitleComponent{
@State title:string='水果排行榜'
@Link fruitModels:FruitModel[]
build() {
Row(){ // 显示返回图标跟标题 Row(){ Image($r('app.media.back')) .width(30) .height(30) .margin({right:10}) .onClick(()=>{ // 退出当前app let context=getContext() as AppContext.UIAbilityContext context.terminateSelf() })
Text(this.title) .fontSize(30)
}.width('50%') .height(40) .justifyContent(FlexAlign.Start)
// 显示刷新图标 Row(){ Image($r('app.media.reset')) .height(30) .width(30) .margin({right:20}) .onClick(()=>{ // 刷新数据 this.fruitModels=[new FruitModel('1','草莓','10000') ,new FruitModel('2','西瓜','9000') ,new FruitModel('3','香蕉','8000') ,new FruitModel('4','梨子','7000') ,new FruitModel('5','枣子','6000') ,new FruitModel('6','山竹','5000') ,new FruitModel('7','桃子','4000') ,new FruitModel('8','西瓜1','3000') ,new FruitModel('9','西瓜2','3000') ,new FruitModel('10','西瓜3','3000') ,new FruitModel('11','西瓜4','3000') ,new FruitModel('12','西瓜5','3000') ,new FruitModel('13','西瓜6','3000') ,new FruitModel('14','西瓜7','3000') ,new FruitModel('15','西瓜8','3000') ,new FruitModel('16','西瓜9','3000') ,new FruitModel('17','西瓜10','3000') ,new FruitModel('18','西瓜11','3000') ,new FruitModel('19','西瓜12','3000') ,new FruitModel('20','西瓜13','3000') ] })
}.width('50%') .height(40) .justifyContent(FlexAlign.End) } .width('100%') .backgroundColor(Color.Pink) .justifyContent(FlexAlign.SpaceBetween)
} }
复制代码

3.5 TableHeaderComponent.ets

/** * 表头组件 */@Component  export default struct TableHeaderComponent {    build() {      Row(){        Text('排名')          .fontSize(20)          .width('20%')          .fontWeight(400)          .fontColor('#989A9C')
Text('种类') .fontSize(20) .width('50%') .fontColor('#989A9C')
Text('得票数') .fontSize(20) .width('30%') .fontWeight(400) .fontColor('#989A9C') }.width('100%') .height(30) .backgroundColor(Color.Green)
} }
复制代码

3.6 ItemComponent.ets

import FruitModel from '../model/FruitModel'
/** * 内容主体 */
@Component export default struct ItemComponent { /** * 水果数据 */ @Link fruitModels: FruitModel[]
build() { Column() { List({space:20}) { ForEach(this.fruitModels, (item: FruitModel) => { ListItem() { Row() { Text(item.id) .fontSize(20) .width('20%') .margin({left:10}) Text(item.name) .fontSize(20) .width('50%') Text(item.vote) .fontSize(20) .width('30%') } .width('100%') } }) } } } }
复制代码


发布于: 17 小时前阅读数: 16
用户头像

享受当下,享受生活,享受成长乐趣! 2025-02-26 加入

鸿蒙、Java、大数据

评论

发布
暂无评论
鸿蒙NEXT开发-组件事件监听和状态管理_东林知识库_InfoQ写作社区