鸿蒙系列--装饰器

news/2024/7/21 9:01:36 标签: harmonyos, 华为

一、基础UI组件结构

        每个UI组件需要定义为@Component struct对象,其内部必须包含一个且只能包含一个build(){}函数,用于绘制UI;struct之内、build()函数之外的地方用于存放数据。

二、基本UI装饰器

@Entry

装饰struct,页面的入口

@Component

装饰struct,表示该struct具有基于组件的能力

@Entry
@Component
struct TestPage {
    build() {
       ……
    }
}

三、数据装饰器

@State 父子相互独立

  • 装饰的变量是组件的局部变量,必须本地初始化,可通过构造参数赋值
  • 当该数据被修改时,所在组件的build()方法会被重新调用,会重新绘制所在UI
子组价:
@Component
export struct ComponentPage {
  @State count: number = 0

  private toggleClick() {
    this.count += 1
  }

  build() {
    Row() {
      Column({ space: 20 }) {
        Button(`这是子组件,${this.count}`)
          .fontSize(24)
          .onClick(this.toggleClick.bind(this))
      }
      .width('100%')
    }
  }
}
父组件:
import { ComponentPage } from "./ComponentPage"

@Entry
@Component
struct StatePage {
  @State count: number = 0

  private toggleClick() {
    this.count += 1
  }

  build() {
    Row() {
      Column({ space: 20 }) {
        Button(`这是父组件,当前值: ${this.count}`)
          .fontSize(24)
          .onClick(this.toggleClick.bind(this))

        //使用默认初始化值
        ComponentPage()
        //设置count初始值为:20
        ComponentPage({ count: 20 })
      }
      .width('100%')
    }.height('100%')
  }
}
描述:
  • 当被点击之后修改了count的值,页面会重新绘制UI
  • 子组件中的count和父组件的count互不影响
  • 可以给子组件构造方法设置初始值
  • 使用@State修饰的变量必须初始化
效果图:

@Prop 父子单向同步

  • 继承@State的所有功能
  • 被其装饰的变量可以和父组件建立单向同步关系。@Prop装饰的变量是可变的,但修改不会同步回父组件,当父组件的@State变化时,本地修改的@Prop会被覆盖
子组件:
@Component
export struct ComponentPage {
  @Prop count: number

  private toggleClick() {
    this.count += 1
  }

  build() {
    Row() {
      Column({ space: 20 }) {
        Button(`这是子组件,${this.count}`)
          .fontSize(24)
          .onClick(this.toggleClick.bind(this))
      }
      .width('100%')
    }
  }
}
父组件:
import { ComponentPage } from "./ComponentPage"

@Entry
@Component
struct StatePage {
  @State count: number = 0

  private toggleClick() {
    this.count += 1
  }

  build() {
    Row() {
      Column({ space: 20 }) {
        Button(`这是父组件,当前值: ${this.count}`)
          .fontSize(24)
          .onClick(this.toggleClick.bind(this))

        ComponentPage({ count: this.count })
      }
      .width('100%')
    }.height('100%')
  }
}
描述:
  • 将父组件的count设置到子组件使用的@Prop修饰的变量时,父组件与子组件这时建立起单向同步
  • 父组件修改值后,子组件跟着修改,子组件修改值父组件不受影响
  • 使用的@Prop修饰的变量不能自己初始化
效果图:

@Link 父子双向同步

  • @Link装饰的变量和父组件构建双向同步关系的状态变量,父组件会接受来自@Link装饰的变量的修改的同步,父组件的更新也会同步给@Link装饰的变量。
  • @Link装饰的变量与其父组件中的数据源共享相同的值
  • @Link装饰器不能在@Entry装饰的自定义组件中使用
子组件:
@Component
export struct ComponentPage {
  @Link count: number

  private toggleClick() {
    this.count += 1
  }

  build() {
    Row() {
      Column({ space: 20 }) {
        Button(`这是子组件,${this.count}`)
          .fontSize(24)
          .onClick(this.toggleClick.bind(this))
      }
      .width('100%')
    }
  }
}
父组件:
import { ComponentPage } from "./ComponentPage"

@Entry
@Component
struct StatePage {
  @State count: number = 0

  private toggleClick() {
    this.count += 1
  }

  build() {
    Row() {
      Column({ space: 20 }) {
        Button(`这是父组件,当前值: ${this.count}`)
          .fontSize(24)
          .onClick(this.toggleClick.bind(this))

        ComponentPage({ count: $count })
      }
      .width('100%')
    }.height('100%')
  }
}
描述:
  • 父组件通过$count来和子组件的@Link修饰的值绑定
  • 绑定之后实现父子双向绑定,修改一端,另一组件也随之变化
  • 使用@Link不能自己初始化
效果图:

@State、@Prop与@Link的异同

相同点:

  • 都会引起UI重绘
  • 内部私有

不同点:

不同点@State@Prop@Link
装饰内容基本数据类型,类,数组基本数据类型基本数据类型,类,数组
关联不与其他控件关联父@State -> 子@Prop 单向关联父@State <-> 子@Link 双向关联
初始化时机声明时创建组件时由参数传入创建组件时由参数传入

四、生产消费的装饰器

@Provide、@Consume

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

案例:

在父组件中将数据多级传递给子组件,子子组件

1.使用@Link修饰的变量进行传递
父组件:
import { ProviderSonPage } from "./ProviderSonPage"

@Entry
@Component
struct ProviderPage {
  @State message: string = '父类A'

  build() {
    Row() {
      Column() {
        Text(this.message).fontSize(50).fontColor(Color.Red)
          .onClick(() => {
            //点击文字  进行切换
            this.message = this.message === '父类A' ? '父类B' : '父类A'
          })
        //调用子组件
        ProviderSonPage({ sonMsg: $message })
      }
      .width('100%')
    }
    .height('100%')
  }
}
子组件:
import { ProviderGrandSonPage } from "./ProviderGrandSonPage"

@Component
export struct ProviderSonPage {
  @Link sonMsg: string

  build() {
    Column() {
      Text(this.sonMsg).fontSize(30).fontColor(Color.Green)
        .onClick(() => {
          this.sonMsg = '我是子类'
        })
      //调用孙子组件:子类的子类
      ProviderGrandSonPage({ grandSonMsg: $sonMsg })
    }
  }
}
子子组件:
@Component
export struct ProviderGrandSonPage {
  @Link grandSonMsg: string

  build() {
    Column() {
      Text(this.grandSonMsg).fontSize(20).fontColor(Color.Blue)
        .onClick(() => {
          this.grandSonMsg = '我是子类的子类'
        })
    }
  }
}
总结:
  • 都需要通过一个多余被@Link修饰的变量进行传递,太过复杂,如果传递层级太深没更加明显
2.发布者订阅者模式

使用发布者Provide和订阅者Consume可以直接传递到子子组件

父组件:
import { ProviderSonPage } from "./ProviderSonPage"

@Entry
@Component
struct ProviderPage {
  @Provide('Mes') message: string = '父类A'
  //也可以写成@Provide message: string = '父类A'
  build() {
    Row() {
      Column() {
        Text(this.message).fontSize(50).fontColor(Color.Red)
          .onClick(() => {

            this.message = this.message === '父类A' ? '父类B' : '父类A'
          })
        //调用子组件时就不再需要传递参数
        ProviderSonPage()
      }
      .width('100%')
    }
    .height('100%')
  }
}
子组件:
import { ProviderGrandSonPage } from "./ProviderGrandSonPage"

@Component
export struct ProviderSonPage {
  @Consume('Mes') sonMsg:string

  build() {
    Column() {
      Text(this.sonMsg).fontSize(30).fontColor(Color.Green)
        .onClick(() => {
          this.sonMsg = '我是子类'
        })
      //调用子组件时就不再需要传递参数
      ProviderGrandSonPage()
    }
  }
}
子子组件:
@Component
export struct ProviderGrandSonPage {
  @Consume('Mes') grandSonMsg:string
  //也可以写成@Consume message:string

  build() {
    Column() {
      Text(this.grandSonMsg).fontSize(20).fontColor(Color.Blue)
        .onClick(() => {
          this.grandSonMsg = '我是子类的子类'
        })
    }
  }
}
总结:
  • 使用发布者订阅者模式,父类使用@Provide,其他需要观察的子类使用@Consume,就可以能实现双向绑定
  • 当层级很深时不需要一层一层的往下传递,直接使用发布者订阅者进行监听就能实现相同的效果
  • @Provide和@Consume可以通过相同的变量名或者相同的变量别名绑定,变量类型必须相同
  • @Provide必须设置初始值,@Consume不可设置默认初始值
  • @Provide修饰的变量和@Consume修饰的变量是一对多的关系

效果图:

五、状态变量更改通知

@Watch:使用观察者模式的装饰器,但该装饰器不是触发变量变化,而是绑定一个函数,当@Watch变量变化时,调用该函数

@Watch和自定义组件更新

子组件:
@Component
export struct TotalViewPage {
  @Prop @Watch('onCountUpdated') count: number;
  @State total: number = 0;
  // @Watch 回调
  onCountUpdated(propName: string): void {
    this.total += this.count;
  }

  build() {
    Text(`Total: ${this.total}`)
  }
}
父组件:
import {TotalViewPage} from "./TotalViewPage"

@Entry
@Component
struct CountModifierPage {
  @State count: number = 0;

  build() {
    Column() {
      Button('add to basket')
        .onClick(() => {
          this.count++
        })
      TotalViewPage({ count: this.count })
    }
  }
}
描述:
  1. CountModifier自定义组件的Button.onClick点击事件自增count
  2. 由于@State count变量更改,子组件TotalView中的@Prop被更新,其@Watch('onCountUpdated')方法被调用,更新了子组件TotalView 中的total变量
  3. 子组件TotalView中的Text重新渲染

@Watch与@Link组合使用

bean对象:PurchaseItem
export class PurchaseItem {
  static NextId: number = 0;
  public id: number;
  public price: number;

  constructor(price: number) {
    this.id = PurchaseItem.NextId++;
    this.price = price;
  }
}
子类:BasketViewer
import {PurchaseItem} from "./PurchaseItem"

@Component
export struct BasketViewer {
  @Link @Watch('onBasketUpdated') shopBasket: PurchaseItem[];
  @State totalPurchase: number = 0;

  updateTotal(): number {
    let total = this.shopBasket.reduce((sum, i) => sum + i.price, 0);
    // 超过100欧元可享受折扣
    if (total >= 100) {
      total = 0.9 * total;
    }
    return total;
  }
  // @Watch 回调
  onBasketUpdated(propName: string): void {
    this.totalPurchase = this.updateTotal();
  }
  build() {
    Column() {
      ForEach(this.shopBasket,
        (item) => {
          Text(`Price: ${item.price.toFixed(2)} €`)
        },
        item => item.id.toString()
      )
      Text(`Total: ${this.totalPurchase.toFixed(2)} €`)
    }
  }
}
父类:BasketModifierPage
import {BasketViewer} from "./BasketViewer"
import {PurchaseItem} from "./PurchaseItem"

@Entry
@Component
struct BasketModifierPage {
  @State shopBasket: PurchaseItem[] = [];
  build() {
    Column() {
      Button('Add to basket')
        .onClick(() => {
          this.shopBasket.push(new PurchaseItem(Math.round(100 * Math.random())))
        })
      BasketViewer({ shopBasket: $shopBasket })
    }
  }
}
描述:
  1. BasketModifierPage组件的Button.onClick向BasketModifier shopBasket中添加条目
  2. @Link装饰的BasketViewer shopBasket值发生变化
  3. 状态管理框架调用@Watch函数BasketViewer onBasketUpdated 更新BasketViewer TotalPurchase的值
  4. @Link shopBasket的改变,新增了数组项,ForEach组件会执行item Builder,渲染构建新的Item项;@State totalPurchase改变,对应的Text组件也重新渲染
  5. 重新渲染是异步发生的

http://www.niftyadmin.cn/n/5302946.html

相关文章

UE5.1_Python使用1

UE5.1_Python使用1 目录 UE5.1_Python使用1 1. 问题及链接: 2. 使用: UE5-Python依赖:

MySQL取出N列里最大or最小的一个数据

如题&#xff0c;现在有3列&#xff0c;都是数字类型&#xff0c;要取出这3列里最大或最小的的一个数字 -- N列取最小 SELECT LEAST(temperature_a,temperature_b,temperature_c) min FROM infrared_heat-- N列取最大 SELECT GREATEST(temperature_a,temperature_b,temperat…

数据结构和算法-B树的插入和删除

文章目录 B树的插入小结B树的删除小结 B树的插入 首先将根节点的关键字个数填满&#xff0c;填满后再分开成树 分开的规则 此时插入90&#xff0c;从根节点依次查找&#xff0c;然后插入到终端节点的关键字中 插入同上&#xff0c;注意此时在终端节点插入要符合终端节点的大…

第三章:工作生活利器:ChatGPT在工作与生活中的热门应用场景

3.1 文案策划&#xff1a;让你的广告文案瞬间引爆热度 在当今竞争激烈的市场环境中&#xff0c;一条出色的广告文案能在短时间内抓住大众的注意力&#xff0c;进而提高品牌知名度和产品销售。然而&#xff0c;撰写出色的文案并非易事&#xff0c;需要创意、洞察力和对市场的了解…

python笔记-自用

2024/1/3# python用号实现字符串的拼接&#xff0c;非字符串不能拼接 from pymysql import Connection# 连接mysql数据库salary 100 name "wang"ans "%s" % salary name print(ans)x 1 y 2 sum "%s %s" % (x, y) print(sum)# %s字符串占…

Zookeeper 分布式服务协调治理框架介绍入门

文章目录 为甚么需要Zookeeper一、Zookeeper 介绍1.1 介绍1.2 Zookeeper中的一些概念1.2.1 集群角色1.2.2 会话 session1.2.3 数据节点 Znode1.2.4 版本1.2.5 事件监听器 Watcher1.2.6 ACL 权限控制表(Access Control Lists) 二、 Zookeeper的系统模型2.1.1 ZNode节点2.1.2 ZNo…

算法分析与设计 第八次理论作业

算法分析与设计 第八次理论作业 文章目录 算法分析与设计 第八次理论作业一. 单选题&#xff08;共1题&#xff0c;10分&#xff09;二. 填空题&#xff08;共3题&#xff0c;30分&#xff09;三. 判断题&#xff08;共3题&#xff0c;30分&#xff09;四. 简答题&#xff08;共…

实现vue加载指令 v-loading

文章目录 为什么使用指令实现 loading具体实现封装准备实现 loading 效果loading 显示与隐藏使用修饰符扩展 完整代码与结语 本文不会详细的说明 vue 中指令这些知识点&#xff0c;如果存在疑问&#xff0c;请自行查阅文档或者其他资料 为什么使用指令实现 loading 在日常的开…