一,什么是装饰器,有什么作用
在鸿蒙(HarmonyOS)开发中,装饰器是一种特殊的声明,可以用于修改类、方法、属性或参数的行为。装饰器的主要作用是提供一种简洁的语法,用于在不修改原有代码结构的情况下,添加额外的功能或修改现有功能。
装饰器的基本概念和作用如下:
1.基本概念:
- 装饰器:装饰器是一种特殊的声明,用于修改类、方法、属性或参数的行为。装饰器通常以“@”符号开头,后面跟随装饰器的名称和一对圆括号。
- 状态管理装饰器:用于管理组件和应用的状态 例如,@State用于声明状态变量,@Observed用于观察类属性的变化。
- 嵌套装饰器:多个装饰器可以组合使用,形成嵌套装饰器。例如,@ObservedV2和@Trace装饰器需要配合使用
2.作用:
- 状态管理:装饰器在状态管理中起到关键作用。例如,@State装饰器用于声明状态变量,当状态变量改变时,UI会重新渲染@Observed和@Trace装饰器用于观察类属性的变化,从而触发UI刷新
- 组件间数据传递:装饰器可以用于在组件之间传递数据例如,@Event装饰器用于子组件向父组件传递数据,实现数据的同步更新
- 类和属性观测:装饰器可以增强对类和属性变化的观测能力 例如,@ObservedV2和@Trace装饰器可以观测嵌套类中属性的变化,从而触发UI刷新
通过使用装饰器,开发者可以更灵活地管理组件状态、实现数据传递和观测类属性的变化,从而提高代码的可维护性和复用性。
二,根据各自作用将装饰器分为以下几种类型(仅接触到的)
1.装饰组件
(1)Entry 定义一个页面 本质也是一个组件。
在HarmonyOSArkTS开发中,@entry装饰器通常用于标识应用程序的入口点。它主要用于在应用启动时执行特定的初始化操作。例如,当应用启动时,系统会自动寻找被@entry装饰的组件或方法,并执行其中的逻辑。
(2)Component 定义一个可复用组件
例:
@Component //自定义了按钮一个组件
struct MyButton {
build() {
Row() {
Button("按钮")
.type(ButtonType.Normal)
.borderRadius(3)
Text()
.width(10)
.height(10)
.backgroundColor("#007DFE")
.translate({ x: -5 })
.rotate({ angle: 45 })
}
}
}
@Entry
@Component
struct Index {
build() {
Column({space:10}) {
MyButton() //引用自己定义的按钮组件
MyButton()
MyButton()
}
.height('100%')
.width('100%')
.backgroundColor(Color.Orange)
}
}
2.对组件,样式及事件进行封装
(1)Builder 最常用可以封装:样式 结构 事件 可传参
例:
@Entry
@Component
struct Index {
@Builder //定义自定义构建函数,函数名为:component
component(photo: string, text: string) {
GridItem(){
Column({ space: 10 }) {
Image(photo)
.width(60)
Text(text)
.fontWeight(700)
}
.margin(20)
}
}
build() {
Column() {
Grid(){
//使用该函数,传参渲染页面
this.component('/imgs//ic_taobao_09.png', '新品')
this.component('/imgs//ic_taobao_08.png', '药品')
this.component('/imgs//ic_taobao_07.png', '阿里')
this.component('/imgs//ic_taobao_06.png', '服饰')
this.component('/imgs//ic_taobao_05.png', '蔬菜')
this.component('/imgs//ic_taobao_04.png', '快递')
}
}
.width('100%')
.height(500)
}
}
(2)Style 仅限于封装通用属性和事件 不可传参
例:
@Styles
function commonStyles() {
.width(100)
.height(50)
.backgroundColor('#ff20e359')
}
@Entry
@Component
struct Index {
@Styles
commonStyles1(){
.borderRadius(10)
}
build() {
Column({ space: 20 }) {
Column()
.commonStyles()
.commonStyles1()
Column()
.commonStyles()
Column()
.commonStyles()
}
.width('100%')
}
}
(3)Extends 可传参 抽取特定组件样式进行封装
例:
@Extend(Text)
function textStyle(num:number,msg?:string){
.fontSize(num)
.fontWeight(700)
.backgroundColor(Color.Orange)
.onClick(()=>{
AlertDialog.show({message:msg})
})
}
@Entry
@Component
struct Index {
build() {
Column({space:30}){
Text('刘备')
.textStyle(20,'牢大')
Text('关羽')
.textStyle(16,'牢二')
Text('张飞')
.textStyle(12,'牢三')
}
.height('100%')
.width('100%')
}
}
(4)BuilderParam 该装饰器用于声明任意UI描述的一个元素,类似slot占位符。允许外部传递UI
例:单个@BuilderParam参数
@Component
struct mycomp {
@Builder hbuilder(){
Text('可替换组件')
}
@BuilderParam
con:()=>void=this.hbuilder
build() {
Column(){
Text('=========================')
this.con() //指定插入位置
Text('=========================')
}
}
}
@Entry
@Component
struct Index {
build() {
Column() {
mycomp()
mycomp() {
Button('插入的按钮')
}
mycomp() {
TextInput()
}
mycomp() {
Image($r('app.media.app_icon'))
.width(60)
}
}
.height('100%')
.width('100%')
.backgroundColor(Color.Orange)
}
}
例:多个@BuilderParam 参数
@Component
struct mycomp {
// 设置默认 的 Builder,避免外部不传入
@Builder
hbuilder() {
Text('可替换组件')
}
// 由外部传入 UI
@BuilderParam
start: () => void = this.hbuilder
@BuilderParam
middle: () => void = this.hbuilder
@BuilderParam
end: () => void = this.hbuilder
build() {
Column() {
Row() {
this.start()
this.middle()
this.end()
}
.justifyContent(FlexAlign.SpaceBetween)
.width('100%')
}
}
}
@Entry
@Component
struct Index {
@Builder
head() {
Text('首页')
}
@Builder
middlend() {
Text('中间')
}
@Builder
end() {
Text('结尾')
}
build() {
Column() {
mycomp({ start: this.head, middle: this.middlend, end: this.end })
}
.padding(20)
.height('100%')
.width('100%')
.backgroundColor(Color.Orange)
}
}
3.组件的状态管理
(1)State 用于声明组件内的状态变量 状态变量改变促使UI改变
- 当装饰的数据类型为boolean、string、number类型时,可以观察到数值的变化。
- 当装饰的数据类型为class或者Object时,可以观察到自身的赋值的变化,和其属性赋值的变化,即Object.keys(observedObject)返回的所有属性。例子如下。声明ClassA和Model类
(2).1 Prop 父->子单项传递 修改值时注意统一数据源
- 修改父组件数据,会同步更新子组件
- 修改子组件@Prop 修饰的数据,子组件 UI 更新,但会被父组件状态更新覆盖
- 通过回调函数的方式修改父组件的数据,然后触发@Prop数据的更新
需求如下: 点击父组件图片,子组件可以显示出来相应的图片
@Component
struct right {
@Prop url: string = '' //定义容器来接收父组件传来的数据
build() {
Column() {
Image($r(this.url)) //子组件操作接收到的数据
.onClick(() => {
console.log('value1', this.url)
})
}
.backgroundColor(Color.Green)
.width(200)
.height(200)
}
}
@Entry
@Component
struct Index {
list: string[] = ['app.media.111', 'app.media.2', 'app.media.3', 'app.media.4']
@State url: string = ''
build() {
Column() {
Row() {
Column() {
Text('父组件')
ForEach(this.list, (item: string) => {
Image($r(item))
.width(100)
.height(100)
.margin(20)
.onClick(() => {
this.url = item
console.log('value', this.url)
})
})
}
.border({ width: 2 })
.width(100)
Column() {
Text('子组件')
right({ url: this.url }) //连接数据纽带:子组件的url=父组件中点击的url
}
.border({ width: 2 })
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
}
.padding(10)
.height('100%')
.width('100%')
.backgroundColor(Color.Orange)
}
}
(2).2 子->父 子组件向父组件传递数据通过父组件传递函数,子组件调用函数的方式实现,否则子组件自己操作会导致子组件数据变化,父组件值仍然不会改变,就是两个数据源。类似于:现如今微信,支付宝等都可以支出,而钱都来自银行,试想以下单独修改子组件(支出微信),父组件(银行)值却没有改变,那岂不是可以比肩马云。
@Component
struct left {
Url: string = ''
/////////////////////第一步:准备接收父亲传过来的函数
geturl:()=>void=()=>{
}
build() {
Column() {
Image($r(this.Url))
.width(100)
.onClick(()=>{
this.geturl() ///////////第二步:调用
console.log('value',this.Url)
})
}
}
}
@Entry
@Component
struct Index {
list: string[] = ['app.media.111', 'app.media.2', 'app.media.3', 'app.media.4']
@State url: string = ''
build() {
Column() {
Row() {
Column() {
ForEach(this.list, (item: string) => {
/////第三步:geturl是父组件定义的修改数据的函数,Url,geturl是子组件接收父组件传递数据的容器
left({ Url: item ,geturl:()=>{
this.url=item
}})
.margin(20)
})
}
.width(100)
Image($r(this.url))
.onClick(()=>{
console.log('value',this.url)
})
.width(200)
.height(200)
.backgroundColor(Color.Red)
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
}
.height('100%')
.width('100%')
.backgroundColor(Color.Orange)
}
}
拓展:this指向问题
- 在哪里定义的箭头函数,箭头函数中this 永远指向谁
- 普通函数this 是谁调用, this指向谁
(3)Link 双向数据传递 仅修饰简单数据类型 不适用于ForEach 适用引用数据类型 不适用深层次引用数据类型
例:
@Component
struct MyButton {
@Link
num: number //////第一步定义接收容器
build() {
Column() {
Button(`儿子的数据 ${this.num}`) /////第二步调用
.onClick(() => {
this.num++
})
}
}
}
@Entry
@Component
struct Index {
@State
num: number = 100
build() {
Column({ space: 10 }) {
Button(`父亲的数据 ${this.num}`)
.onClick(() => {
this.num++
})
// ====
MyButton({ num: this.num }) //////第三步:父子连接通道
}
.width("100%")
.height("100%")
.justifyContent(FlexAlign.Center)
}
}
(4)Observed 和 ObjectLink 可直接修改深层次属性 按需优化:数据改变,整个组件不需要重新加载,仅加载数据的改变,可以很大程度降低内存消耗
作用:之前了解的修改嵌套对象的写法,会让页面刷新,降低用户体验。为了解决这个问题,可以通过 @Observed 与 @ObjectLink 这两个装饰器解决
使用步骤:
- interface 改为 类 class 并使用 @Observed 修饰
- 通过 new 的方式完成数据的创建(通过 new 的方式来监测数据的改变)
- 状态修饰符改为 @ObjectLink
-
- 在父组件修改数据:不需要 splice
- 在子组件修改数据
举例
- 优化前:*
class Person {
num: number = 100
avatar: string = 'https://picx.zhimg.com/027729d02bdf060e24973c3726fea9da_l.jpg?source=06d4cd63'
}
@Component
struct MyItem {
@Prop
item: Person
add = () => {
}
build() {
Row() {
Image(this.item.avatar)
.width(60)
Text(this.item.num.toString())
}
.onClick(this.add)
}
}
@Entry
@Component
struct Index {
@State
personList: Person[] = [
new Person(),
new Person()
]
build() {
Column() {
ForEach(this.personList, (item: Person, index: number) => {
MyItem({
item: item, add: () => {
this.personList[index].num++
//由于是深层次属性,页面更新需要用到数组API:splice,不适用于@link
this.personList.splice(index,1,item)
console.log('value',this.personList[index].num)
}
})
})
}
.width("100%")
.height("100%")
.justifyContent(FlexAlign.Center)
}
}
- 优化后:*
// 类型 只能使用class
@Observed //仅适用于Class类
class Person {
num: number = 100
avatar: string = 'https://picx.zhimg.com/027729d02bdf060e24973c3726fea9da_l.jpg?source=06d4cd63'
}
@Component
struct MyItem {
@ObjectLink
item: Person
add = () => {
}
build() {
Row() {
Image(this.item.avatar)
.width(60)
Text(this.item.num.toString())
}
.onClick(this.add)
}
}
@Entry
@Component
struct Index {
@State
personList: Person[] = [
new Person(), //建立对象,实例化
new Person()
]
build() {
Column() {
ForEach(this.personList, (item: Person, index: number) => {
MyItem({
item: item, add: () => {
this.personList[index].num++ //无需调用数组API
}
})
})
}
.width("100%")
.height("100%")
.justifyContent(FlexAlign.Center)
}
}
(5)Provice Consume 爷孙组件 跨组件传参
例:
interface Person {
num: number
changeNum: () => void
}
@Entry
@Component
struct Index {
////////提供给孙子的属性,方法
@Provide
person: Person = {
num: 200, changeNum: () => {
this.person.num++
}
}
build() {
Column() {
Text(`爷爷 ${this.person.num}`)
.fontSize(40)
.onClick(() => {
this.person.num++
})
AAA()
}
.width("100%")
.height(300)
.border({
width: 5,
color: Color.Yellow
})
.justifyContent(FlexAlign.Center)
}
}
@Component
struct AAA {
build() {
Column() {
BBB()
}
.width("100%")
.height(200)
.border({
width: 5,
color: Color.Blue
})
.justifyContent(FlexAlign.Center)
}
}
@Component
struct BBB {
// 接收爷爷传来的属性方法
@Consume
person: Person
build() {
Column() {
Text(`孙子组件 ${this.person.num}`)
.fontSize(20)
.onClick(this.person.changeNum)
}
.width("100%")
.height(100)
.border({
width: 5,
color: Color.Red
})
}
}
(6)Watch 监听某个数据 发生改变, 然后做出其他的业务处理。如果开发者需要关注某个状态变量的值是否改变,可以使用 @Watch 为状态变量设置回调函数。
- @State、@Prop、@Link 等装饰器在 @Watch 装饰之前
- Watch无法单独使用,必须配合状态装饰器,比如@State、@Prop、@Link 基本语法:
@State
@Watch('方法名')
info:string=''
方法名(){
// 数据改变之后会触发
}
- 只能用普通方法,箭头函数会报错,不考虑
例
注意:
- 初始有两个偶数,所以点击第一下加2—3的值,因为下一个可能是奇数
- Data.now()用的时间戳添加给数组(类似于随机数),是不是偶数有不确定性
时间戳:是从1970年1月1日(UTC/GMT的午夜)开始所经过的秒数(不考虑闰秒),用于表示一个时间点
@Entry
@Component
struct Index {
@State
@Watch('GetLength')
list:number[]=[3,1,16,2]
@State num:number=0
GetLength(){
this.num=this.list.filter(n=>n%2==0).length
}
build() {
Column({space:20}){
Button(`数组长度${this.list.length}`)
.fontSize(30)
.onClick(()=>{
this.list.push(Date.now())
console.log('value',Date.now())
})
Text(`数组偶数个数 ${this.num}`)
.fontSize(30)
}
.height('100%')
.width('100%')
.backgroundColor(Color.Orange)
}
}
拓展:@Link 或者 @Observed 和 @objectLink使用场景
-
@Link 用不了 无法和foreach中item关联
-
@Observed 和 @objectLink
- class
- 拆分成组件
-
任何修饰器 都做不到 儿子修改父亲 深层次数据 , 两个改变都不到
- 数据改变
- UI改变
-
@objectLink 能做到 儿子修改父亲 深层次的数据
- 数据变
- ui不变
-
真要做到 儿子修改父亲 深层次数 ui变化和数据变化
- this.list.splice
- this.list[index] = xxxx