4.UI 范式基本语法
1.概述
在初步了解了ArkTS语言之后,我们以一个具体的示例来说明ArkTS的基本组成。如下图所示,当开发者点击按钮时,文本内容从“Hello World”变为“Hello ArkUI”。
图1 示例效果图
本示例中,ArkTS的基本组成如下所示。
图2 ArkTS的基本组成
说明
自定义变量不能与基础通用属性/事件名重复。
- 装饰器: 用于装饰类、结构、方法以及变量,并赋予其特殊的含义。如上述示例中@Entry、@Component和@State都是装饰器,@Component表示自定义组件,@Entry表示该自定义组件为入口组件,@State表示组件中的状态变量,状态变量变化会触发UI刷新。
- UI描述:以声明式的方式来描述UI的结构,例如build()方法中的代码块。
- 自定义组件:可复用的UI单元,可组合其他组件,如上述被@Component装饰的struct Hello。
- 系统组件:ArkUI框架中默认内置的基础和容器组件,可直接被开发者调用,比如示例中的Column、Text、Divider、Button。
- 属性方法:组件可以通过链式调用配置多项属性,如fontSize()、width()、height()、backgroundColor()等。
- 事件方法:组件可以通过链式调用设置多个事件的响应逻辑,如跟随在Button后面的onClick()。
- 系统组件、属性方法、事件方法具体使用可参考基于ArkTS的声明式开发范式。
除此之外,ArkTS扩展了多种语法范式来使开发更加便捷:
- @Builder/@BuilderParam:特殊的封装UI描述的方法,细粒度的封装和复用UI描述。
- @Extend/@Styles:扩展内置组件和封装属性样式,更灵活地组合内置组件。
- stateStyles:多态样式,可以依据组件的内部状态的不同,设置不同样式。
2.声明式 UI描述
ArkTS以声明方式组合和扩展组件来描述应用程序的UI,同时还提供了基本的属性、事件和子组件配置方法,帮助开发者实现应用交互逻辑。
1. 创建组件
根据组件构造方法的不同,创建组件包含有参数和无参数两种方式。
说明
创建组件时不需要new运算符。
无参数
如果组件的接口定义没有包含必选构造参数,则组件后面的“()”不需要配置任何内容。例如,Divider组件不包含构造参数。
Column() {
Text('item 1')
Divider()
Text('item 2')
}
有参数
如果组件的接口定义包含构造参数,则在组件后面的“()”需要配置相应参数。
Image('https://xyz/test.jpg')
// string类型的参数
Text('test')
// $r形式引入应用资源,可应用于多语言场景
Text($r('app.string.title_value'))
// 无参数形式
Text()
- 变量或表达式也可以用于参数赋值,其中表达式返回的结果类型必须满足参数类型要求。
Image(this.imagePath)
Image('https://' + this.imageUrl)
Text(`count: ${this.count}`)
2. 配置属性
属性方法以“.”链式调用的方式配置系统组件的样式和其他属性,建议每个属性方法单独写一行。
Text('test')
.fontSize(12)
Image('test.jpg')
.alt('error.jpg')
.width(100)
.height(100)
Text('hello')
.fontSize(this.size)
Image('test.jpg')
.width(this.count % 2 === 0 ? 100 : 200)
.height(this.offset + 100)
- 对于系统组件,ArkUI还为其属性预定义了一些枚举类型供开发者调用,枚举类型可以作为参数传递,但必须满足参数类型要求。
Text('hello')
.fontSize(20)
.fontColor(Color.Red)
.fontWeight(FontWeight.Bold)
3. 配置事件
事件方法以“.”链式调用的方式配置系统组件支持的事件,建议每个事件方法单独写一行。
Button('Click me')
.onClick(() => {
this.myText = 'ArkUI';
})
使用箭头函数表达式配置组件的事件方法,要求使用“() => {...}”,以确保函数与组件绑定,同时符合ArkTS语法规范。
Button('add counter')
.onClick(() => {
this.counter += 2;
})
使用组件的成员函数配置组件的事件方法,需要bind this。ArkTS语法不推荐使用成员函数配合bind this去配置组件的事件方法。
myClickHandler(): void {
this.counter += 2;
}
...
Button('add counter')
.onClick(this.myClickHandler.bind(this))
fn = () => {
console.info(`counter: ${this.counter}`)
this.counter++
}
...
Button('add counter')
.onClick(this.fn)
说明
箭头函数内部的this是词法作用域,由上下文确定。匿名函数可能会有this指向不明确问题,在ArkTS中不允许使用。
4. 配置子组件
如果组件支持子组件配置,则需在尾随闭包"{...}"中为组件添加子组件的UI描述。Column、Row、Stack、Grid、List等组件都是容器组件。
Column() {
Text('Hello')
.fontSize(100)
Divider()
Text(this.myText)
.fontSize(100)
.fontColor(Color.Red)
}
Column() {
Row() {
Image('test1.jpg')
.width(100)
.height(100)
Button('click +1')
.onClick(() => {
console.info('+1 clicked!');
})
}
}
3.样式和结构重用
随着页面复杂程度提高,页面中会有很多的样式&结构代码,其中难免重复的部分,如果可以提取出来重复使用,就可以提升编码效率,减少重复代码,提升代码可读性。
咱们来来学习3 种样式&结构复用的语法:
- @Styles: 抽取公共样式、事件
- @Extends:扩展组件样式、事件
- @Builder:轻量级的元素复用机制(结构、样式、事件)- 重点掌握
1.@Styles
示例
不同于@Extend 用来给某个组件扩展,@Styles 可以抽取 通用的事件和属性供所有组件使用
注意:
- @Styles 不支持传递参数
- 可以继续添加样式覆盖 @Styles 中定义的内容
- 组件内部写法,可以通过 this 访问组件属性
2.@Extend
通过 @Extend 可以扩展组件的样式、事件,实现复用效果
基本语法:.ets组件的最上方编写代码
注意:
① 扩展的组件和使用的组件同名,比如 @Extend(Text) ,那么后续通过 Text 才可以点出该扩展
② function 的名字不能与组件本身拥有的属性同名,自身属性优先级更高
例如:Text组件上本身自带有一个fontStyle属性,我们就不能再@Extend(Text) function fontStyle()一个同名方法了
③ 只能在当前组件有用,不能跨组件使用
3.@Builder
如果连结构也需要抽取,就可以使用@Builder,他也可以叫做 自定义构建函数
// ---------------------------------全局Builder
@Builder
function GlobalTextItem(title: string) {
Text(title)
.fontSize(30)
.onClick(() => {
// 逻辑略
})
}
@Entry
@Component
struct Day01_04_Builder {
@State message: string = '@Builder';
build() {
Column({ space: 20 }) {
// 使用全局 Builder
GlobalTextItem('你好')
// 使用本地 Builder
this.LocalTextItem('西兰花炒蛋')
}
.width('100%')
.height('100%')
}
// ----------------------组件内部builder
@Builder
LocalTextItem(title: string) {
Text(title)
.fontSize(30)
.onClick(() => {
// 逻辑略
})
}
}
总结
名称 | 适合 | 参数 |
@Styles | 抽取 公共样式、事件 | 不可以传递参数 |
@Extend | 抽取 特定组件 样式、事件 | 可以传递参数 |
@Builder(重点掌握) | 抽取 结构、样式、事件 | 可以传递参数 |
4.自定义组件
1.概述
在ArkUI中,UI显示的内容均为组件,由框架直接提供的称为系统组件,由开发者定义的称为自定义组件。在进行 UI 界面开发时,通常不是简单的将系统组件进行组合使用,而是需要考虑代码可复用性、业务逻辑与UI分离,后续版本演进等因素。因此,将UI和部分业务逻辑封装成自定义组件是不可或缺的能力。
- struct:自定义组件基于struct实现,struct + 自定义组件名 + {...}的组合构成自定义组件,不能有继承关系。对于struct的实例化,可以省略new。
- @Component:@Component装饰器仅能装饰struct关键字声明的数据结构。
- build()函数:build()函数用于定义自定义组件的声明式UI描述,自定义组件必须定义build()函数。
- @Entry:@Entry装饰的自定义组件将作为UI页面的入口。在单个UI页面中,最多可以使用@Entry装饰一个自定义组件。
- @Preview:如果想要单独预览组件,可以使用@Preview 进行装饰
注意:自定义组件必须导出后,才能在其他组件中导入使用
export struct SonCom {}
父传子
子传父
2.页面路由(router模式)
1.概述
页面路由指的是在应用程序中实现不同页面之间的跳转,以及数据传递。
我们先明确自定义组件和页面的关系:
- 自定义组件:@Component 装饰的UI单元,
- 页面:即应用的UI页面。可以由一个或者多个自定义组件组成。
-
- @Entry装饰的自定义组件为页面的入口组件,即页面的根节点,一个页面有且仅能有一个@Entry
通过 Router 模块就可以实现这个功能. import { router } from '@kit.ArkUI'
步骤:
- 创建页面 -> 页面与组件不同的地方是有且只有一个入口组件( @Entry修饰),并且在
src/main/resources/base/profile/main_pages.json
有配置好了页面路径
-
- 口诀:一入口,一配置
- router控制页面跳转
- 带参数跳转并获取
2.pushUrl 与 replaceUrl
先来看看 pushUrl的情况
- 默认打开首页 → 首页入栈
- pushUrl 去详情页 → 详情页入栈
- back 返回上一页 → 详情页出栈
- 此时页面栈中应该只有一个页面
整一个过程中,都可以 router.getLength 进行查看
再来看看replaceUrl的情况
- 默认打开首页 → 首页入栈
- replaceUrl 去详情页 → 详情页替换首页,首页销毁
- back 无法返回 → 没有上一页
跳转到登录页面时可以使用 replaceUrl,因为无需在页面栈中保存其他页面的页面栈信息了。
页面栈相关 api
// 获取页面栈长度
router.getLength()
// 获取页面状态
let page = router.getState();
console.log('页面栈数量:' + page.index);
console.log('跳转的路由:' + page.name);
console.log('路由路径:' + page.path);
// 清空页面栈
// router.clear()
3.路由模式
路由提供了两种不同的跳转模式:
- standard(标准实例模式)
- Single(单实例模式)
不同模式的决定了页面是否会创建多个实例:
- Standard:无论之前是否添加过,一直添加到页面栈【默认】
-
- 场景:适用于每次跳转都呈现全新内容或状态的场景,避免数据展示紊乱(数据变化)
- Single:如果之前加过页面,会使用之前添加的页面【需要添加参数手动修改】
-
- 场景:适用于那些需要保留页面状态或避免重复创建相同页面的场景(数据不变化)
路由模式语法:
总结
路由跳转的方式有pushUrl和replaceUrl
pushUrl的跳转要考虑两种模式:标准模式,单例模式
- pushUrl({},模式为标准模式(默认的模式)) -> 页面栈中的表现形式
- pushUrl({},模式为单例模式) -> 页面栈中的表现形式
- replaceUrl() -> 跳转以后的前一个页面自动销毁了
5.@BuilderParam装饰器:引用@Builder函数
1.概述
当开发者创建了自定义组件,并想对该组件添加特定功能,例如想在某一个指定的自定义组件中添加一个点击跳转操作,此时若直接在组件内嵌入事件方法,将会导致所有该自定义组件的实例都增加了功能。为解决此问题,ArkUI引入了@BuilderParam装饰器,@BuilderParam用来装饰指向@Builder方法的变量(@BuilderParam是用来承接@Builder函数的)。开发者可以在初始化自定义组件时,使用不同的方式(如:参数修改、尾随闭包、借用箭头函数等)对@BuilderParam装饰的自定义构建函数进行传参赋值,在自定义组件内部通过调用@BuilderParam为组件增加特定的功能。该装饰器用于声明任意UI描述的一个元素,类似slot占位符。
简单来说就是父组件既可以传值/函数也可以传 UI 组件
2.用法
1.单个@BuilderParam
使用尾随闭包的方式传入:
- 组件内有且仅有一个使用 @BuilderParam 装饰的属性,即可使用尾随闭包
- 内容直接在 {} 传入即可
注意:
- 此场景下自定义组件不支持使用通用属性。
2.多个@BuilderParam
- 自定义组件-定义:
-
- 添加多个 @BuilderParam ,并定义默认值
- 自定义组件-使用
-
- 通过参数的形式传入多个 Builder,比如
SonCom({ titleBuilder: this.fTitleBuilder, contentBuilder: this.fContentBuilder })
6. 页面和自定义组件的生命周期
组件和页面在创建、显示、销毁的这一整个过程中,会自动执行 一系列的【生命周期钩子】,其实就是一系列的【函数】,让开发者有机会在特定的阶段运行自己的代码
页面生命周期,即被@Entry装饰的组件生命周期,提供以下生命周期接口:
- onPageShow:页面每次显示时触发一次,包括路由过程、应用进入前台等场景,如果不能触发aboutToAppear函数的时候,网络请求数据代码写在onPageShow中
- onPageHide:页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景。
- onBackPress:当用户点击返回按钮时触发。
组件生命周期,即一般用@Component装饰的自定义组件的生命周期,提供以下生命周期接口:
- aboutToAppear:组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其build()函数之前执行,通常在这里发送网络请求获取数据
- onDidBuild:组件build()函数执行完成之后回调该接口,开发者可以在这个阶段进行埋点数据上报等不影响实际UI的功能。不建议在onDidBuild函数中更改状态变量、这可能会导致不稳定的UI表现。
- aboutToDisappear:aboutToDisappear函数在自定义组件析构销毁之前执行。不允许在aboutToDisappear函数中改变状态变量
import { router } from '@kit.ArkUI'
interface iParams {
name: string
age:number
}
@Entry
@Component
struct Second {
params:iParams = {
name:'',
age:0
}
// 1.组件生命周期
aboutToAppear(): void {
this.params = router.getParams() as iParams
console.log('aboutToAppear--组件即将出现')
}
//2.组件生命周期
onDidBuild() {
console.info('onDidBuild--组件已经构建完成');
}
//3.页面的生命周期
onPageShow() {
console.info('onPageShow---页面显示');
}
// 4页面的生命周期
onPageHide() {
console.info('onPageHide--页面隐藏');
}
//5.组件生命周期
aboutToDisappear() {
console.info('aboutToDisappear--组件销毁了');
}
// 只有被@Entry装饰的组件才可以调用页面的生命周期
onBackPress() {
console.info('onBackPress--返回按钮');
return true; // 返回true表示页面自己处理返回逻辑,不进行页面路由;返回false表示使用默认的路由返回逻辑,不设置返回值按照false处理
}
build() {
Column(){
Text(`欢迎您${this.params.name}`)
Button('返回').onClick(()=>{
router.back()
})
}
.width('100%').height('100%').backgroundColor(Color.Pink)
}
}