ArkTS基础
基于:HarmonyOS开发者3.1/4.0版本配套的开发者文档,对应API能力级别为API 9 Release
文章目录
基本语法
基本语法概述
- 装饰器:用于修饰类、结构、方法以及变量,并赋予其特殊含义
- UI描述:以声明的方式来描述UI的结构,可组合其他组件,如上述被@Component装饰的struct Hello
- 自定义组件:可复用的UI单元,可组合其他组件
- 系统组件:ArkUI框架中默认内置的基础和容器组件,可直接调用
- 属性方法:组件可以通过链式调用配置多项属性
- 事件方法:组件可以通过链式调用设置多个事件的相应逻辑
声明式UI描述
ARkTS以声明方式组合和扩展组件来描述应用程序的UI,还提供了基本的属性、事件、子组件配置方法
创建组件
创建组件包含有参数和无参数两种方式(具体根据组件的构造方法)
如果组件的接口定义没有包含必选构造参数,则组件后面的()
不需要配置任何内容,如:
Column() {
Text('item 1')
Divider()
Text('item 2')
}
如果组件的接口定义包含构造参数,可以在组件后面的()
配置相应参数:
// string类型的参数
Text('test')
// $r形式引入应用资源,可应用于多语言场景
Text($r('app.string.title_value'))
// 无参数形式
Text()
// 变量或表达式也可以用于参数赋值,表达式返回的结果类型必须满足参数类型要求
Text(`count: ${this.count}`)
配置属性
属性方法以.
链式调用的方式配置系统组件的样式和其他属性,建议每个属性方法单独写一行
// 配置Text组件字体的大小
Text('test')
.fontSize(12)
// 配置组件的多个属性,还可以传入变量或者表达式
Image('test.jpg')
.alt('error.jpg')
.width(this.count % 2 === 0 ? 100 : 200)
.height(this.offset + 100)
// 对于一些系统组件,ArkUI还为其预定义了一些枚举类型供调用
Text('hello')
.fontSize(20)
.fontColor(Color.Red)
.fontWeight(FontWeight.Bold)
配置事件
事件方法以.
链式调用的方式配置系统组件支持的事件,建议每个事件方法单独写一行
// 使用剪头函数配置组件的事件方法
Button('Click me')
.onClick(() => {
this.myText = 'ArkUI';
})
// 使用匿名函数,要求使用bind,以确保函数中的this指向当前组件
Button('add counter')
.onClick(function(){
this.counter += 2;
}.bind(this))
// 使用组件成员函数
myClickHandler(): void {
this.counter += 2;
}
...
Button('add counter')
.onClick(this.myClickHandler.bind(this))
配置子组件
如果组件支持子组件配置,则需要再尾随闭包{...}
中为组件添加子组件的UI描述(组件嵌套)
Column() {
Text('Hello')
.fontSize(100)
Divider()
Text(this.myText)
.fontSize(100)
.fontColor(Color.Red)
}
自定义组件
自定义组件具有以下特点:
- 可组合:允许组合使用系统组件及其属性和方法
- 可重用:自定义组件可以被其他组件重用
- 数据驱动UI更新:通过状态量的改变,来驱动UI的刷新
自定义组件的基本结构
- struct:自定义组件基于struct实现,struct + 自定义组件名 + {…}的组合构成自定义组件,不能有继承关系
- @Component:@Component装饰器仅能装饰struct关键字声明的数据结构。struct被@Component装饰后具备组件化的能力,需要实现build方法描述UI,一个struct只能被一个@Component装饰
- build()函数:build()函数用于定义自定义组件的声明式UI描述,自定义组件必须定义build()函数
- @Entry:@Entry装饰的自定义组件将作为UI页面的入口。在单个UI页面中,最多可以使用@Entry装饰一个自定义组件。@Entry可以接受一个可选的LocalStorage的参数
成员函数/变量
成员函数具有以下约束:
- 不支持静态函数
- 成员函数的访问始终是私有的
自定义组件的成员变量有以下约束:
- 不支持静态成员变量
- 所有成员变量都是私有的
- 自定义组件的成员变量本地初始化有些是可选的,有些是必选的
@Component
struct MyComponent {
private countDownFrom: number = 0;
private color: Color = Color.Blue;
build() {
}
}
@Entry
@Component
struct ParentComponent {
private someColor: Color = Color.Pink;
build() {
Column() {
// 创建MyComponent实例,并将创建MyComponent成员变量countDownFrom初始化为10,将成员变量color初始化为this.someColor
MyComponent({ countDownFrom: 10, color: this.someColor })
}
}
}
build()函数
所有声明在build函数的语言,统称为UI描述语言,需要遵循:
- @Entry装饰的自定义组件,其build()函数下的根节点唯一且必要,且必须为容器组件,其中ForEach禁止作为根节点
- @Component装饰的自定义组件,其build()函数下的根节点唯一且必要,可以为非容器组件,其中ForEach禁止作为根节点
- 不允许声明本地变量
- 不允许使用console.info
- 不允许创建本地的作用域
- 不允许调用除了被@Builder装饰的方法以外的方法
- 不允许switch语法,如果需要使用条件判断,请使用if
- 不允许使用表达式
自定义组件通用样式
自定义组件通过“.”链式调用的形式设置通用样式
设置样式时并不是直接设置给自定义组件的,相当于给自定义组件套一个不可见的容器,样式被设置给这个容器
生命周期
页面生命周期如下:
- onPageShow:页面每次显示时触发
- onPageHide:页面每次隐藏时触发一次
- onBackPress:用户点击返回按钮时触发
组件生命周期:
- aboutToAppear:组件即将出现时回调该接口(创建组件实例后调用build前)
- aboutToDisappear:组件即将析构销毁时执行
自定义组件的创建和渲染流程
- 创建自定义组件
- 初始化自定义组件的成员变量
- 执行aboutToAppear
- 在首次渲染的时候,执行build,否则执行onPageShow
第4步会保存两个map:
- 状态变量–>UI组件(包括ForEach和if)
- UI组件–>此组件的更新函数
自定义组件的重新渲染
当事件句柄被触发改变了状态变量时,或者LocalStorage / AppStorage中的属性更改,并导致绑定的状态量更改其值时:
- 框架观察到了变化,启动重新渲染
- 根据框架持有的两个map,找到被这些状态变量管理的UI组件,以及这些UI组件对应的更新函数,执行这些更新函数,实现最小化的更新
自定义组件的删除
如果if组件的分支改变,或者ForEach循环渲染中数组的个数改变,组件将被删除:
- 调用aboutToDisappear(ArkUI的节点删除机制是:后端节点直接从组件树上摘下,后端节点被销毁,对前端节点解引用,当前端节点已经没有引用时,将被JS虚拟机垃圾回收)
- 删除自定义组件和他的变量
@Builder装饰器:自定义构建函数
ArkUI还提供了更轻量化的UI元素复用机制@Builder,被@Builder修饰的函数遵循build函数语法规则,开发者可将简单的UI元素抽象成一个方法,在build中调用
@Builder修饰的函数也叫自定义构建函数
装饰器使用说明
自定义组件内自定义构建函数
定义的语法:
@Builder MyBuilderFunction({ ... })
使用方法:
this.MyBuilderFunction({ ... })
- 允许定义多个自定义构建函数,被认为是组件私有的、特殊类型的成员函数
- 自定义构建函数可以再所属组件的build方法和其他自定义构建中使用,但不允许在组件外使用
- this指代的是当前所属组件,组件的状态状态变量可以在自定义构建函数中使用
全局自定义构建函数
定义的语法:
@Builder function MyGlobalBuilderFunction({ ... })
使用方法:
MyGlobalBuilderFunction()
- 全局的自定义函数可以被整个应用获取,不允许使用this和bind方法
- 如果不涉及的组件的变化,建议使用全局的自定义构建函数
参数传递规则
自定义构建函数的参数传递有按值传递和按引用传递两种方式,需要遵循以下规则:
- 参数类型必须和声明的类型一致,不允许undefined、null和返回undefined和null的表达式
- 在自定义构建函数内部,不允许改变参数值,
- 构建函数内语法遵循UI语法规则
按引用传递参数
传递的参数可以为状态变量,且状态变量的改变会引起@Builder方法内的UI刷新,传递方式为$$
@Builder function ABuilder($$: { paramA1: string }) {
Row() {
Text(`UseStateVarByReference: ${$$.paramA1} `)
}
}
@Entry
@Component
struct Parent {
@State label: string = 'Hello';
build() {
Column() {
// 在Parent组件中调用ABuilder的时候,将this.label引用传递给ABuilder
ABuilder({ paramA1: this.label })
Button('Click me').onClick(() => {
// 点击“Click me”后,UI从“Hello”刷新为“ArkUI”
this.label = 'ArkUI';
})
}
}
}
按值传递参数
传递的参数发生变化时,不会引起@Builder内UI刷新
@Builder function ABuilder(paramA1: string) {
Row() {
Text(`UseStateVarByValue: ${paramA1} `)
}
}
@Entry
@Component
struct Parent {
label: string = 'Hello';
build() {
Column() {
ABuilder(this.label)
}
}
}
简单理解:按值传递相当于一次性调用构造函数,按引用传递相当于绑定
@BuilderParam装饰器:引用@Builder函数
当开发者创建了自定义组件,并想对该组件添加特定功能时,比如添加一个点击跳转操作。如果直接在组件内嵌入方法,那么所有引用这个组件的页面就都添加了这个功能,这可能不是开发者想要的。ArkUI引入了@BuilderParam装饰器,来装饰指向@Builder方法的变量,类似于slot占位符,在指定位置加入自己想要的东西,并且不会影响其他使用这个组件的地方。
装饰器使用说明
初始化@BuilderParam装饰的方法
@BuilderParam装饰的方法只能被自定义构建函数初始化
-
使用所属自定义组件的自定义构建函数或者全局的自定义构建函数,在本地初始化@BuilderParam
@Builder function GlobalBuilder0() {} @Component struct Child { @Builder doNothingBuilder() {}; @BuilderParam aBuilder0: () => void = this.doNothingBuilder; @BuilderParam aBuilder1: () => void = GlobalBuilder0; build(){} }
-
用父组件自定义构建函数初始化子组件@BuilderParam装饰的方法
@Component struct Child { @BuilderParam aBuilder0: () => void; build() { Column() { this.aBuilder0() } } } @Entry @Component struct Parent { @Builder componentBuilder() { Text(`Parent builder `) } build() { Column() { Child({ aBuilder0: this.componentBuilder }) } } }
显示效果如下图:
这里官方文档没有说明,但是子组件定义slot的时候,给出的样例中,给slot赋值了一个默认的空构造器,猜测slot需要空构造器来占位,当父组件不指定该slot的时候,渲染过程中实际调用了空构造函数,而不是没渲染这个slot。
在开发中,需要注意this指向正确。
尾随闭包初始化组件
在自定义组件中使用@BuilderParam装饰的属性时也可通过尾随闭包进行初始化。在初始化自定义组件时,组件后紧跟一个大括号“{}”形成尾随闭包场景。(其实可以理解为定义一个匿名函数,当做参数传递给slot)
// xxx.ets
@Component
struct CustomContainer {
@Prop header: string;
@BuilderParam closer: () => void
build() {
Column() {
Text(this.header)
.fontSize(30)
this.closer()
}
}
}
@Builder function specificParam(label1: string, label2: string) {
Column() {
Text(label1)
.fontSize(30)
Text(label2)
.fontSize(30)
}
}
@Entry
@Component
struct CustomContainerUser {
@State text: string = 'header';
build() {
Column() {
// 创建CustomContainer,在创建CustomContainer时,通过其后紧跟一个大括号“{}”形成尾随闭包
// 作为传递给子组件CustomContainer @BuilderParam closer: () => void的参数
CustomContainer({ header: this.text }) {
Column() {
specificParam('testA', 'testB')
}.backgroundColor(Color.Yellow)
.onClick(() => {
this.text = 'changeHeader';
})
}
}
}
}
效果如下(点击黄色区域,会改变标题为‘changeHeader’):
@Styles装饰器:定义组件重用样式
提供了提炼公共样式进行复用的装饰器@Styles。
@Styles装饰器可以将多条样式设置提炼成一个方法,直接在组件声明的位置调用。
装饰器使用说明
-
当前@Styles仅支持通用属性和通用事件
-
@Styles不支持参数
-
@Styles可以定义在组件内或全局,在全局定义时需在方法名前面添加function关键字,组件内定义时则不需要添加function关键字
// 全局 @Styles function functionName() { ... } // 在组件内 @Component struct FancyUse { @Styles fancy() { .height(100) } }
-
定义在组件内的@Styles可以通过this访问组件的常量和状态变量,并可以在@Styles里通过事件来改变状态变量的值
-
组件内的@Styles的优先级高于全局@Styles
@Extend装饰器:定义扩展组件样式
在@Styles的基础上,提供了@Extend,用于扩展原生组件的样式。
装饰器使用说明
@Extend(UIComponentName) function functionName { ... }
使用规则
-
和@Styles不同,@Extend仅支持定义在全局,不支持组件内部定义
-
和@Styles不同,@Extend支持封装指定的组件的私有属性和私有事件和预定义相同组件的@Extend方法
// @Extend(Text)可以支持Text的私有属性fontColor @Extend(Text) function fancy () { .fontColor(Color.Red) } // superFancyText可以调用预定义的fancy @Extend(Text) function superFancyText(size:number) { .fontSize(size) .fancy() }
-
和@Styles不同,@Extend装饰的方法支持参数,开发者可以在调用时传递参数,需要遵循TS方法传值调用
-
@Extend装饰的方法的参数可以作为function,作为Event事件的句柄
@Extend(Text) function makeMeClick(onClick: () => void) { .backgroundColor(Color.Blue) .onClick(onClick) } @Entry @Component struct FancyUse { @State label: string = 'Hello World'; onClickHandler() { this.label = 'Hello ArkUI'; } build() { Row({ space: 10 }) { Text(`${this.label}`) .makeMeClick(this.onClickHandler.bind(this)) } } }
-
@Extend的参数可以作为状态变量,当状态变量改变时,UI可以正常的被刷新渲染
stateStyles:多态样式
@Styles和@Extend仅仅应用于静态页面的样式复用,是stateStyles可以依据组件的内部状态的不同,快速的设置不同样式。
概述
stateStyles是属性方法,可以根据UI内部状态来设置样式,类似于css伪类,但语法不同。ArkUI提供四种状态:
- focused:获焦态
- normal:正常态
- pressed:按压态
- disabled:不可用态
@Styles和stateStyles联合使用
@Entry
@Component
struct MyComponent {
@Styles normalStyle() {
.backgroundColor(Color.Gray)
}
@Styles pressedStyle() {
.backgroundColor(Color.Red)
}
build() {
Column() {
Text('Text1')
.fontSize(50)
.fontColor(Color.White)
.stateStyles({
normal: this.normalStyle,
pressed: this.pressedStyle,
})
}
}
}
在stateStyles里可以使用常规变量和状态变量
@Entry
@Component
struct CompWithInlineStateStyles {
@State focusedColor: Color = Color.Red;
normalColor: Color = Color.Green
build() {
Column() {
Button('clickMe').height(100).width(100)
.stateStyles({
normal: {
.backgroundColor(this.normalColor)
},
focused: {
.backgroundColor(this.focusedColor)
}
})
.onClick(() => {
this.focusedColor = Color.Pink
})
.margin('30%')
}
}
}