ArkUI提供了多种装饰器,通过使用这些装饰器,状态变量不仅可以观察在组件内的改变,还可以在不同组件层级间传递,比如父子组件、跨组件层级,也可以观察全局范围内的变化。根据状态变量的影响范围,将所有的装饰器可以大致分为:
- 管理组件拥有状态的装饰器: 组件级别的状态管理,可以观察组件内变化,和不同组件层级的变化,但需要唯一观察同一个组件树上,即同一个页面内。
- 管理应用拥有状态的装饰器: 应用级别的状态管理,可以观察不同页面,甚至不同UIAbility的状态变化,是应用内全局的状态管理
1.@Entry
@Entry 修饰器的主要作用是标记一个类为ArkUI应用的入口组件。在ArkUI或HarmonyOS应用开发中,每个页面组件或都对应一个类,而这个类代表的@Entry组件是修饰应用的器入口就是点用来。告诉这框架意呀,当应用启动时,系统会首先加载并渲染这个被@Entry修饰的组件。
@Entry
@Component
struct 组件名{
build(){
}
}
2.@Component
@Component 修饰器(Decorator)通常用于定义一个类为ArkUI的组件。一个ets可以包含很多的组件@Component,并且他们之间可以通过各种修饰器进行相互传参数
@Entry
@Component
struct 组件A{
build(){
}
}
@Component
struct 组件B{
build(){
}
}
3.@Extend
通过Extend可以拓展组件的样式,事件实现复用的效果
语法:
// 1. 定义在全局
@Extend(组件名)
function functionName(参数1....) {
.属性()
.事件(()=>{})
}
// 2. 使用
组件名(){}
.functionName(参数1...)
那么通过以下实例来进行讲解
没有进行@Extend抽取前的代码
Swiper() {
Text('0')
.textAlign(TextAlign.Center)
.backgroundColor(Color.Red)
.fontColor(Color.White)
.fontSize(30)
.onClick(() => {
AlertDialog.show({
message: '轮播图 1'
})
})
Text('1')
.textAlign(TextAlign.Center)
.backgroundColor(Color.Green)
.fontColor(Color.White)
.fontSize(30)
.onClick(() => {
AlertDialog.show({
message: '轮播图 2'
})
})
Text('2')
.textAlign(TextAlign.Center)
.backgroundColor(Color.Blue)
.fontColor(Color.White)
.fontSize(30)
.onClick(() => {
AlertDialog.show({
message: '轮播图 3'
})
})
}
通过@Extend抽取Text的属性
//1.定义,抽取
@Extend(Text)
function texteExtend(){
.textAlign(TextAlign.Center)
.backgroundColor(Color.Blue)
.fontColor(Color.White)
.fontSize(30)
.onClick(() => {
AlertDialog.show({
message: '轮播图 3'
})
})
}
@Entry
@Component
struct Index{
build(){
Swiper() {
Text('0')
.textExtend(Color.Red,'轮播图 1')
Text('1')
.textExtend(Color.Green,'轮播图 2')
Text('2')
.textExtend(Color.Blue,'轮播图 3')
}
}
}
注意:它可以对某一个组件的属性进行抽取封装,如果当有其中的一个属性的值不一样时,就可以采取传参的方式进行它的改变。但是它只能定义在全局
4.@Styles
不同于前面的@Extend用来给某个组件进行拓展封装,@Styles可以抽取通用的事件和属性给多个组件来使用。
语法:
// 全局定义
@Styles function functionName() {
.通用属性()
.通用事件(()=>{})
}
@Component
struct FancyUse {
// 在组件内定义
@Styles fancy() {
.通用属性()
.通用事件(()=>{})
}
}
// 使用
组件().fancy()
组件().functionName()
举例:
没有进行抽取的代码:
@Entry
@Component
struct Index {
@State message: string = '@styles';
@State bgColor: ResourceColor = Color.Gray
build() {
Column({ space: 10 }) {
Text(this.message)
.width(100)
.height(100)
.backgroundColor(this.bgColor)
.onClick(() => {
this.bgColor = Color.Orange
})
Column() {
}
.width(100)
.height(100)
.backgroundColor(this.bgColor)
.onClick(() => {
this.bgColor = Color.Orange
})
Button('按钮')
.width(100)
.height(100)
.backgroundColor(this.bgColor)
.onClick(() => {
this.bgColor = Color.Orange
})
}
.width('100%')
.height('100%')
}
}
使用@Style抽取后
//全局定义写法
@Styles
function globalSize() {
.width(100)
.height(100)
}
@Entry
@Component
struct Index {
@State message: string = '@styles';
@State bgColor: ResourceColor = Color.Gray
build() {
Column({ space: 10 }) {
Text(this.message)
.width(100)
.height(100)
.backgroundColor(this.bgColor)
.onClick(() => {
this.bgColor = Color.Orange
})
Column() {
}
//使用
.globalSize()
.sizeAndColorFancy()
Button('按钮')
.width(100)
.height(100)
.backgroundColor(this.bgColor)
.onClick(() => {
this.bgColor = Color.Orange
})
}
.globalSize()
}
//局部定义写法
@Styles
sizeAndColorFancy() {
.backgroundColor(this.bgColor)
.onClick(() => {
this.bgColor = Color.Orange
})
}
}
注意:它只能抽取一些通用的属性和通用的事件,对于一些组件的私有属性它不能进行抽取,而且@Style不能进行传参,它有全局写法和局部写法两种。如果在组件中设置了@Style包含的通用属性的话,会将@Style定义的属性覆盖
5. @Builder
如果连结果也需要进行抽取的话,就可以使用@Builder来进行抽取。
语法:
// 自定义 全局 构建函数
@Builder function MyGlobalBuilderFunction(param1,param2...) {
// 结构、属性、事件放这里
}
// 使用
MyGlobalBuilderFunction(param1,param2...)
// 自定义 组件内 构建函数
@Builder MyBuilderFunction( param1,param2...) {
// 结构、属性、事件放这里
}
// 使用
this.MyBuilderFunction(param1,param2...)
没有没有抽取前的代码:
@Entry
@Component
struct Index {
@State message: string = '@Builder';
build() {
Column({ space: 20 }) {
Text(this.message)
.fontSize(30)
Row() {
Row() {
Column({ space: 10 }) {
Image($r('app.media.ic_reuse_01'))
.width('80%')
Text('阿里拍卖')
}
.width('25%')
.onClick(() => {
AlertDialog.show({
message: '点了 阿里拍卖'
})
})
Column({ space: 10 }) {
Image($r('app.media.ic_reuse_02'))
.width('80%')
Text('菜鸟')
}
.width('25%')
.onClick(() => {
AlertDialog.show({
message: '点了 菜鸟'
})
})
Column({ space: 10 }) {
Image($r('app.media.ic_reuse_03'))
.width('80%')
Text('芭芭农场')
}
.width('25%')
.onClick(() => {
AlertDialog.show({
message: '点了 芭芭农场'
})
})
Column({ space: 10 }) {
Image($r('app.media.ic_reuse_04'))
.width('80%')
Text('阿里药房')
}
.width('25%')
.onClick(() => {
AlertDialog.show({
message: '点了 阿里药房'
})
})
}
}
}
.width('100%')
.height('100%')
}
}
进行抽取后:
// 1.全局 Builder
@Builder
function navItem(icon: ResourceStr, text: string) {
Column({ space: 10 }) {
Image(icon)
.width('80%');
Text(text);
}
.width('25%')
.onClick(() => {
AlertDialog.show({
message: '点了' + text
})
})
}
@Entry
@Component
struct Index {
@State message: string = '@Builder';
build() {
Column({ space: 20 }) {
Text(this.message)
.fontSize(30)
Row() {
//3. 这两个使用全局的
navItem($r('app.media.ic_reuse_01'), '阿里拍卖')
navItem($r('app.media.ic_reuse_02'), '菜鸟')
//4. 这两个使用本地的
this.navItem($r('app.media.ic_reuse_03'), '芭芭农场')
this.navItem($r('app.media.ic_reuse_04'), '阿里药房')
}
}
.width('100%')
.height('100%')
}
// 2.局部 Builder
@Builder
navItem(icon: ResourceStr, text: string) {
Column({ space: 10 }) {
Image(icon)
.width('80%');
Text(text);
}
.width('25%')
.onClick(() => {
AlertDialog.show({
message: '点了' + text
})
})
}
}
注意:@Builder有两种写法,可以写在全局以及局部,它可以进行抽取结构,事件,属性,样式,功能强大,也可以进行传参,开发中常用。
6.@State
在ArkTS中,被@State修饰的变量称为状态变量,它的更改可以引起UI的刷新。
// for simple type
@State count: number = 0;
// value changing can be observed
this.count = 1;
7.@BuilderParam
@BuilderParam该装饰器用于声明任意UI描述的一个元素,类似slot占位符。总而言之就是自定义组件允许外部传递UI
7.1.单个@BuilderParam参数
举个例子:
@Component
struct mybutton {
@Builder
// 1.占位符构建函数,里面的内容可写可不写
IsShow() {
}
@BuilderParam
// 2.() => void表示的是一个函数类型,abc是自己定义的一个函数名称
abc: () => void = this.IsShow
build() {
Column() {
Text('===================')
// 3.这里的这个this.abc()是拿来给下面的mybutton()里面的内容占位的
this.abc()
Text('==================')
}.height(300)
.width(300)
.border({
width:10,
color:Color.Black
})
.borderRadius(150)
}
}
@Entry
@Component
struct Index {
build() {
Column() {
mybutton(){
// 4.因为上面已经有this.abc()来给自己占位了,所以这里面的东西就能从主组件进行传入
Image($r('app.media.app_icon'))
.width(30)
Text('我的东西')
}
}.width('100%')
.height('100%')
}
}
使用尾随闭包的方式传入:
- 组件内有且仅有一个使用 @BuilderParam 装饰的属性,即可使用尾随闭包
- 内容直接在 {} 传入即可
注意:
- 此场景下自定义组件不支持使用通用属性。**
7.2.多个@BuilderParam 参数
子组件有多个BuilderParam,必须通过参数的方式来传入
@Component
struct mySelf {
@Builder
//占位符构建函数,里面的内容可写可不写
IsShow() {
Text('占位符')
}
@BuilderParam
//() => void表示的是一个函数类型,abc是自己定义的一个名称
a: () => void = this.IsShow
@BuilderParam
//() => void表示的是一个函数类型,abc是自己定义的一个名称
b: () => void = this.IsShow
@BuilderParam
//() => void表示的是一个函数类型,abc是自己定义的一个名称
c: () => void = this.IsShow
//子组件的内容
build() {
Row() {
this.a()
this.b()
this.c()
}.backgroundColor(Color.White)
.justifyContent(FlexAlign.SpaceBetween)
.width('100%')
.padding(20)
}
}
@Entry
@Component
struct Index {
@Builder
a1() {
Text('返回')
.fontSize(30)
}
@Builder
b1() {
Text('首页')
.fontSize(30)
}
@Builder
c1() {
Text('菜单')
.fontSize(30)
}
//父组件的内容
build() {
Row() {
mySelf({
a:this.a1,
b:this.b1,
c:this.c1
})
}.width('100%')
.height('100%')
.backgroundColor(Color.Gray)
}
}
说明:它需要与@Builder配合使用
8.@Prop
#@Prop装饰的变量可以和父组件建立单向的同步关系。@Prop 装饰的变量是可变的,但是变化不会同步回其父组件。
8.1.简单类型
对于简单类型 string、number、boolean、enum,可以直接在子组件里面进行数据修改,而且会引起UI改变以及父组件的数据更改。但是要同步给父组件需要使用回调函数(箭头函数),否则只是父亲的单方向同步给子组件
@Component
struct SonCom {
@Prop info: string
changeInfo = (newInfo: string) => {
}
build() {
Button('info:' + this.info)
.onClick(() => {
this.changeInfo('改啦')
})
}
}
@Entry
@Component
struct FatherCom {
@State info: string = '么么哒'
build() {
Column() {
Text(this.info)
SonCom({
info: this.info,
changeInfo: (newInfo: string) => {
this.info = newInfo
}
})
}
.padding(20)
.backgroundColor(Color.Orange)
}
}
8.2.引用数据类型(复杂类型)
复杂类型的做法类似,通过回调函数将需要修改的数据传递给父组件即可
interface User {
name: string
age: number
}
@Entry
@Component
struct Index {
@State
userInfo: User = {
name: 'jack',
age: 18
}
build() {
Column({ space: 20 }) {
Text('父组件')
.fontSize(30)
Text('用户名:' + this.userInfo.name)
.white()
.onClick(() => {
this.userInfo.name = 'rose'
})
Text('年龄:' + this.userInfo.age)
.white()
.onClick(() => {
this.userInfo.age++
})
Child({
user: this.userInfo,
userChange: (newUser: User) => {
this.userInfo.name = newUser.name
this.userInfo.age = newUser.age
}
})
}
.width('100%')
.height('100%')
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
.backgroundColor(Color.Pink)
}
}
@Component
struct Child {
@Prop
user: User
userChange: (newUser: User) => void = (newUser: User) => {
}
build() {
Text('子组件:' + JSON.stringify(this.user))
.padding(10)
.backgroundColor('#0094ff')
.fontColor(Color.White)
.onClick(() => {
this.userChange({
name: '路飞',
age: 26
})
})
}
}
@Extend(Text)
function white() {
.fontSize(20)
.fontColor(Color.White)
}
9.@Link
使用@Link 可以实现父组件和子组件的双向同步,对于简单类型以及复杂类型的变量可以做到双向的数据同步,但是对于深层次的引用类型,它做不到。
9.1.简单类型
@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)
}
}
说明:父组件和子组件的数据是同步的,做到了统一数据源。
9.2.复杂类型
interface IPerson {
age: number
}
@Component
struct MyButtbon {
@Link
person: IPerson
build() {
Column() {
Button(`子组件的数据 ${this.person.age}`)
.onClick(() => {
this.person.age++
})
}
}
}
@Entry
@Component
struct Index {
@State
person: IPerson = {
age: 100
}
build() {
Column({ space: 10 }) {
Button(`父亲的数据 ${this.person.age}`)
.onClick(() => {
//对象里面可以直接用点++,在对象数组里面不可以点++,如this.starList[index].hot++
this.person.age++
})
// =====
MyButtbon({ person: this.person })
}
.width("100%")
.height("100%")
.justifyContent(FlexAlign.Center)
}
}
说明:父组件和子组件的数据是同步的,做到了统一数据源。
9.3.深层次的引用类型
1.@Link对于深层次的引用类型数据无法修饰,以下的代码会报错
2.@Link对与ForEach循环出来的item也无法进行修饰
直接看例子:
import { promptAction } from '@kit.ArkUI'
interface IDog {
weight: number
}
interface IPerson {
// dog 引用数据类型 深层次 (引用数据中又嵌套了引用数据类型)
dog: IDog
}
@Component
struct MyButtbon {
//@Link //无法运用于深层引用数据类型,这样会使子组件无法显示
dog: IDog
build() {
Column() {
Button(`子组件中的数据 ${this.dog.weight}`)
}
}
}
@Entry
@Component
struct Index {
@State
person: IPerson = {
dog: {
weight: 200
}
}
build() {
Column({ space: 10 }) {
Button(`父组件中的数据 ${this.person.dog.weight}`)
.onClick(() => {
// 鸿蒙没有对深层次的数据 做监听 ------
// UI 不会更新
// 数据变化了,但是this.person.dog.weight++没有在UI上面渲染
// this.person.dog.weight++ // 无效
// 数组.splice
// let item = this.list[index] //通过item来获取到一个新的数组对象,在使用item.splice来获取想要的数据
// this.list.age++ //对象数组类型这样写无法使age值在UI上面渲染
//解决方法:
const weight = this.person.dog.weight + 1
this.person.dog = { weight: weight }
// this.list[index] = xxxxx
promptAction.showToast({ message: `${this.person.dog.weight}` })
})
// 无法使用@Link来双向同步,这里只能父传子
MyButtbon({ dog: this.person.dog })
}
.width("100%")
.height("100%")
.justifyContent(FlexAlign.Center)
}
}
10.@Watch 监听器
如果需要关注某个状态变量的值是否改变,可以使用@Watch 为状态变量设置回调函数。
注意:
Watch无法单独使用,必须配合状态装饰器,比如@State、@Prop、@Link
语法:
@State
@Watch('方法名')
info:string=''
方法名(){
// 数据改变之后会触发
}
举例:
@Entry
@Component
struct Index {
@State num: number = 0
用
@Watch('getnum')
@State list: number[] = []
getnum() {
//获取每个时间戳是否为偶数,如果为偶数就取下来放进新的数组在取它的长度
this.num = this.list.filter(item => item % 2 == 0).length
}
build() {
Column() {
Text(`偶数的个数:${this.num}`)
Text(`数组长度:${this.list.length}`)
.onClick(() => {
this.list.push(Date.now())
})
}.backgroundColor(Color.Pink)
.justifyContent(FlexAlign.Center)
.width('100%')
.height('100%')
}
}
11.@Observed 和 @ObjectLink
之前都是修改嵌套对象的写法,会让页面刷新,降低用户的体验,为了解决这个问题,接下来@Observed 和@ObjectLink 这两个装饰器解决。
语法:
// @Observed 装饰类
@Observed
class ClassA{
// 略
}
// 子组件
@Component
struct Com{
@ObjectLink
数据:ClassA
}
注意:
1. @Observed只能修饰类,构造的数据必须要进行new的方式出现
2.@ObjectLink 只能用在子组件身上
3.这两个配合可以对深层次的属性进行修改,并且生效
比如类似这种深层次的属性:
this.person.dog.weight++ // 可以生效
4.@objectLink能做到子组件修改父组件的深层次数据,但是修改的数据不能在UI上面进行改变,它跟其他的装饰器不同的是只有@objectLink能做到子组件修改父组件的深层次数据
12.@Provice和@Consume
将数据传递给后代,和后代的数据进行双向同步
语法:
// 写法 1:通过相同的变量名绑定
@Provide a: number = 0;
@Consume a: number;
// 写法 2通过相同的变量别名绑定
@Provide b: number = 0;
@Consume('b') c: number;
举例:
interface IPrson {
num: number
add: () => void
}
@Component
struct Son {
//儿子也需要用上Consume,用上Prop是undefined
//num: number
build() {
Column() {
Text(`儿子组件组件 `)
SunZi()
}.backgroundColor(Color.Pink)
.width(300)
.height(200)
.border({
width: 5,
color: Color.Gray
})
}
}
@Component
struct SunZi {
//num: number
//探究二
@Consume
person: IPrson[]
//这个变量名要跟爷爷组件传过来的变量名相同,而且不能给它进行赋值
//探究一
@Consume
Function: IPrson
build() {
Column() {
//探究一
Text(`孙子组件 ${this.Function.num}`)
.onClick(this.Function.add)
//探究二
Text(`${this.person[0].num}`)
.onClick(() => {
this.person[0].num++
})
}.backgroundColor(Color.Pink)
.width(120)
.height(100)
.border({
width: 5,
color: Color.Gray
})
}
}
@Entry
@Component
struct Index {
//1.num: number = 100 //对于简单类型可以直接双向绑定,并且UI生效
//1.探究一
@Provide
person: IPrson[] = [{
num: 0,
add: () => {
}
}]
//2.探究二:对于深层次引用类型的需要这样改数据
@Provide
Function: IPrson = {
num: 0,
add: () => {
this.Function.num++
}
}
build() {
Column() {
//探究一
Text(`爷爷组件 ${this.Function.num}`)
.onClick(this.Function.add) //——>括号里面相等于() => {
// this.Function.num++
// }
/*.onClick(()=>{
this.Function.add() //——>这一行代码相等于this.Function.num++
})*/
//探究二
Text(`${this.person[0].num}`)
.onClick(() => {
this.person[0].num++
AlertDialog.show({message:this.person[0].num.toString()})
})
Son()
}.backgroundColor(Color.Pink)
.width('100%')
.height(300)
.border({
width: 5,
color: Color.Gray
})
}
}
总结:
- 子孙
@Provide和@Consume可以对简单类型,引用类型直接绑定,而且还可以引起UI生效。
写法1:
如:1.发送@Provide
2.接收@Consume
写法2:
如:1.发送@Provide
2.接收@Consume
3.孙子组件里面修改数据(能修改数据,但是不能进行UI渲染改变)
@Provide和@Consume也可以使用在爸爸与儿子的身份关系上,用法不变
总结:
只有@Observed和@objectLink这一组可以对深层的引用类型,在二层组件或者三层组件进行对数据直接更改,同时还能进行统一数据源,但是不会引起UI改变。其他的修饰器都不可以。