ArkUI框架 - UI范式
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:多态样式,可以依据组件的内部状态的不同,设置不同样式。
创建自定义组件
自定义组件具有以下特点:
- 可组合:允许开发者组合使用系统组件、及其属性和方法。
- 可重用:自定义组件 可以被其他组件重用,并作为不同的实例在不同的父组件或容器中使用。
- 数据驱动UI更新:通过状态变量的改变,来驱动UI的刷新。
自定义组件的基本用法
{ message: string = 'Hello, World!';build() {// HelloComponent自定义组件组合系统组件Row和TextRow() {Text(this.message).onClick(() => {// 状态变量message的改变驱动UI刷新,UI从'Hello, World!'刷新为'Hello, ArkUI!'this.message = 'Hello, ArkUI!';})}}
}
struct HelloComponent
注意
如果在另外的文件中引用该自定义组件,需要使用export关键字导出,并在使用的页面import该自定义组件。
HelloComponent可以在其他自定义组件中的build()函数中多次创建,实现自定义组件的重用。
{build() {Column() {Text('ArkUI message')HelloComponent({ message: 'Hello World!' });Divider()HelloComponent({ message: '你好,世界!' });}}
}
struct ParentComponent
页面和自定义组件生命周期
自定义组件和页面的关系:
- 自定义组件:@Component装饰的UI单元,可以组合多个系统组件实现UI的复用,可以调用组件的生命周期。
- 页面:即应用的UI页面。可以由一个或者多个自定义组件组成,@Entry装饰的自定义组件为页面的入口组件,即页面的根节点,一个页面有且仅能有一个@Entry。只有被@Entry装饰的组件才可以调用页面的生命周期。
页面生命周期,即被@Entry装饰的组件生命周期,提供以下生命周期接口:
- onPageShow:页面每次显示时触发一次,包括路由过程、应用进入前台等场景。
- onPageHide:页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景。
- onBackPress:当用户点击返回按钮时触发。
组件生命周期,即一般用@Component装饰的自定义组件的生命周期,提供以下生命周期接口:
-
aboutToAppear:组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其build()函数之前执行。
-
onDidBuild:组件build()函数执行完成之后回调该接口,开发者可以在这个阶段进行埋点数据上报等不影响实际UI的功能。不建议在onDidBuild函数中更改状态变量、使用animateTo等功能,这可能会导致不稳定的UI表现。
-
aboutToDisappear:aboutToDisappear函数在自定义组件析构销毁之前执行。不允许在aboutToDisappear函数中改变状态变量,特别是@Link变量的修改可能会导致应用程序行为不稳定。
生命周期流程如下图所示,下图展示的是被@Entry装饰的组件(页面)生命周期。
循环渲染
ForEach接口基于数组类型数据来进行循环渲染,需要与容器组件配合使用
键值生成规则
在ForEach循环渲染过程中,系统会为每个数组元素生成一个唯一且持久的键值,用于标识对应的组件。当这个键值变化时,ArkUI框架将视为该数组元素已被替换或修改,并会基于新的键值创建一个新的组件。
ForEach提供了一个名为keyGenerator的参数,这是一个函数,开发者可以通过它自定义键值的生成规则。如果开发者没有定义keyGenerator函数,则ArkUI框架会使用默认的键值生成函数,即(item: Object, index: number) => { return index + ‘__’ + JSON.stringify(item); }。
ArkUI框架对于ForEach的键值生成有一套特定的判断规则,这主要与itemGenerator函数的第二个参数index以及keyGenerator函数的第二个参数index有关,具体的键值生成规则判断逻辑如下图所示。
正确渲染并保证效率的ForEach写法是:
ForEach(this.simpleList, (item: string) => {ChildItem({ item: item })
}, (item: string) => item.id) // 需要保证key唯一
示例:在文章列表卡片上点击“点赞”按钮,从而修改文章的点赞数量。
Article类被@Observed装饰器修饰。父组件ArticleListView传入Article对象实例给子组件ArticleCard,子组件使用@ObjectLink装饰器接收该实例。
当点击第1个文章卡片上的点赞图标时,会触发ArticleCard组件的handleLiked函数。
该函数修改第1个卡片对应组件里article实例的isLiked和likesCount属性值。
article实例是@ObjectLink装饰的状态变量,它的属性值变化,触发对应的ArticleCard组件渲染,读取到的isLiked和likesCount为修改后的新值。
class Article {id: string;title: string;brief: string;isLiked: boolean;likesCount: number;constructor(id: string, title: string, brief: string, isLiked: boolean, likesCount: number) {this.id = id;this.title = title;this.brief = brief;this.isLiked = isLiked;this.likesCount = likesCount;}
}
struct ArticleListView { articleList: Array<Article> = [new Article('001', '第0篇文章', '文章简介内容', false, 100),new Article('002', '第1篇文章', '文章简介内容', false, 100),new Article('003', '第2篇文章', '文章简介内容', false, 100),new Article('004', '第4篇文章', '文章简介内容', false, 100),new Article('005', '第5篇文章', '文章简介内容', false, 100),new Article('006', '第6篇文章', '文章简介内容', false, 100),];build() {List() {ForEach(this.articleList, (item: Article) => {ListItem() {ArticleCard({article: item}).margin({ top: 20 })}}, (item: Article) => item.id)}.padding(20).scrollBar(BarState.Off).backgroundColor(0xF1F3F5)}
}
struct ArticleCard { article: Article;handleLiked() {this.article.isLiked = !this.article.isLiked;this.article.likesCount = this.article.isLiked ? this.article.likesCount + 1 : this.article.likesCount - 1;}build() {Row() {// 此处'app.media.icon'仅作示例,请开发者自行替换,否则imageSource创建失败会导致后续无法正常执行。Image($r('app.media.icon')).width(80).height(80).margin({ right: 20 })Column() {Text(this.article.title).fontSize(20).margin({ bottom: 8 })Text(this.article.brief).fontSize(16).fontColor(Color.Gray).margin({ bottom: 8 })Row() {// 此处app.media.iconLiked','app.media.iconUnLiked'仅作示例,请开发者自行替换,否则imageSource创建失败会导致后续无法正常执行。Image(this.article.isLiked ? $r('app.media.iconLiked') : $r('app.media.iconUnLiked')).width(24).height(24).margin({ right: 8 })Text(this.article.likesCount.toString()).fontSize(16)}.onClick(() => this.handleLiked()).justifyContent(FlexAlign.Center)}.alignItems(HorizontalAlign.Start).width('80%').height('100%')}.padding(20).borderRadius(12).backgroundColor('#FFECECEC').height(120).width('100%').justifyContent(FlexAlign.SpaceBetween)}
}
上述代码的初始运行效果(左图)和点击第1个文章卡片上的点赞图标后的运行效果(右图)如下图所示。