【鸿蒙开发】第四章 ArkTS语言UI范式-基础语法

server/2024/11/15 0:24:51/

目录​​​​​​​

1 前言

2 基本语法

2.1 声明式UI

2.1.1 组件创建

2.1.2 配置属性

2.1.3 配置事件

2.1.4 配置子组件

2.2 自定义组件

2.2.1 成员函数/变量

2.2.2 build()函数

2.3 页面和自定义组件生命周期

2.3.1 自定义组件的创建和渲染流程 

2.3.2 自定义组件重新渲染

2.3.3 自定义组件的删除


1 前言

  1. 基本语法:ArkTS定义了声明式UI描述自定义组件动态扩展UI元素的能力,再配合ArkUI开发框架中的系统组件及其相关的事件方法属性方法等共同构成了UI开发的主体。
  2. 状态管理:ArkTS提供了多维度的状态管理机制。在UI开发框架中,与UI相关联的数据可以在组件内使用,也可以在不同组件层级间传递,比如父子组件之间、爷孙组件之间,还可以在应用全局范围内传递或跨设备传递。另外,从数据的传递形式来看,可分为只读的单向传递和可变更的双向传递。开发者可以灵活的利用这些能力来实现数据和UI的联动。
  3. 渲染控制:ArkTS提供了渲染控制的能力。条件渲染可根据应用的不同状态,渲染对应状态下的UI内容。循环渲染可从数据源中迭代获取数据,并在每次迭代过程中创建相应的组件。数据懒加载从数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。

本章节我们先来展开学习一下基本语法,后续文章会介绍到状态管理渲染控制另外两方面的详细知识。

2 基本语法

  1. 装饰器: 用于装饰类、结构、方法以及变量,并赋予其特殊的含义。如上述示例中@Entry、@Component和@State都是装饰器,@Component表示自定义组件@Entry表示该自定义组件为入口组件@State表示组件中的状态变量,状态变量变化会触发UI刷新
  2. UI描述:以声明式的方式来描述UI的结构,例如build()方法中的代码块。
  3. 自定义组件可复用的UI单元,可组合其他组件,如上述被@Component装饰的struct Hello。
  4. 系统组件:ArkUI框架中默认内置的基础和容器组件,可直接被开发者调用,比如示例中的Column、Text、Divider、Button。
  5. 属性方法:组件可以通过链式调用配置多项属性,如fontSize()、width()、height()、backgroundColor()等。
  6. 事件方法:组件可以通过链式调用设置多个事件响应逻辑,如跟随在Button后面的onClick()。系统组件、属性方法、事件方法具体使用可参考基于ArkTS的声明式开发范式。
  7. @Builder/@BuilderParam特殊的封装UI描述的方法,细粒度的封装和复用UI描述。
  8. @Extend/@Style:扩展内置组件和封装属性样式,更灵活地组合内置组件。
  9. stateStyles多态样式,可以依据组件的内部状态的不同,设置不同样式。

2.1 声明式UI


通过前面章节,我们知道ArkTS以声明方式组合和扩展组件来描述应用程序的UI,同时还提供了基本的属性事件子组件配置方法,帮助开发者实现应用交互逻辑。

2.1.1 组件创建


根据组件构造方法的不同,创建组件包含有参数无参数两种方式。创建组件时不需要new运算符。

如果组件的接口定义没有包含必选构造参数,则组件后面的“()”不需要配置任何内容。如果组件的接口定义包含构造参数,则在组件后面的“()”配置相应参数

// Image的参数为必选参数
Image('https://xyz/test.jpg')
// Text为可选参数,有无参数都可
Text($r('app.string.title_value'))
Text()

2.1.2 配置属性

属性方法“.”链式调用的方式配置系统组件的样式和其他属性,建议每个属性方法单独写一行。

属性方法中参数根据定义,传递常量参数变量参数枚举类型表达式

Text('hello').width(this.count % 2 === 0 ? 100 : 200)    .height(this.offset + 100).fontSize(20).fontColor(Color.Red).fontWeight(FontWeight.Bold)

2.1.3 配置事件

事件方法也以“.”链式调用的方式配置系统组件支持的事件,建议每个事件方法单独写一行。

Button('add counter').onClick(() => {this.counter += 2;})// 使用声明的箭头函数,可以直接调用,不需要bind this。
fn = () => {console.info(`counter: ${this.counter}`)this.counter++
}
...
Button('add counter').onClick(this.fn)

2.1.4 配置子组件

如果组件支持子组件配置,则需在尾随闭包"{…}"中为组件添加子组件的UI描述ColumnRowStackGridList等组件都是容器组件

Column() {Row() {Image('test1.jpg').width(100).height(100)Button('click +1').onClick(() => {console.info('+1 clicked!');})}
}

2.2 自定义组件

自定义组件即开发者根据自己的业务要求定义组件,代码可复用性业务逻辑UI分离,后续版本演进等因素。因此,将UI和部分业务逻辑封装成自定义组件是不可或缺的能力。

自定义组件具有以下特点:

  1. 可组合:允许开发者组合使用系统组件、及其属性方法
  2. 可重用:自定义组件可以被其他组件重用,并作为不同的实例在不同的父组件容器中使用。
  3. 数据驱动UI更新:通过状态变量的改变,来驱动UI的刷新

下面我们以一个简单的自定义组件案例来学习:

@Entry
@Component
export struct HelloComponent {@State message: string = 'Hello, World!';build() {// HelloComponent自定义组件组合系统组件Row和TextRow() {Text(this.message).onClick(() => {// 状态变量message的改变驱动UI刷新,UI从'Hello, World!'刷新为'Hello, ArkUI!'this.message = 'Hello, ArkUI!';})}}
}@Entry
@Component
struct ParentComponent {build() {Column() {// 创建HelloComponent实例,并初始化成员变量messageMyComponent({ message: 'Hello,World!My name is Yvan' })}}
}
  1. export:如果在另外的文件中引用该自定义组件,需要使用export关键字导出,并在使用的页面import该自定义组件。
  2. struct:自定义组件基于struct实现,struct + 自定义组件名 + {…}的组合构成自定义组件,不能有继承关系。对于struct的实例化,可以省略new。
  3. @Component:仅能装饰struct关键字声明的数据结构。struct被@Component装饰后具备组件化的能力,需要实现build方法描述UI,一个struct只能被一个@Component装饰。
  4. build():build()函数用于定义自定义组件的声明式UI描述,自定义组件必须定义build()函数。
  5. @Entry:@Entry装饰的自定义组件将作为UI页面的入口。在单个UI页面中,最多可以使用@Entry装饰一个自定义组件。@Entry可以接受一个可选的LocalStorage的参数。(从API version 10开始,@Entry可以接受一个可选的LocalStorage的参数或者一个可选的EntryOptions参数。)
名称类型必填说明
routeNamestring表示作为命名路由页面的名字。
storageLocalStorage页面级的UI状态存储。
@Entry({ routeName : 'myPage' })
@Component
struct MyComponent {
}
  1. @Reusable:@Reusable装饰的自定义组件具备可复用能力
@Reusable
@Component
struct MyComponent {
}

2.2.1 成员函数/变量

自定义组件中函数和变量,都具备以下约束

  1. 不支持静态
  2. 访问私有化

而变量具体是否需要本地初始化,是否需要从父组件通过参数传递初始化子组件的成员变量,具体后续文章状态管理的内容会介绍。

2.2.2 build()函数

所有声明在build()函数的语言,我们统称为UI描述
我们先来看看这个案例:

@Entry
@Component
struct MyComponent {@State count: number = 1;build() {// 1.根节点唯一且必要,必须为容器组件Row() {ChildComponent() }// 2.不允许声明本地变量// let a: number = 1;// 3.不允许console.info// console.info('print debug log');// 4.不允许本地作用域//{//	...//}// 5.不能调用没有用@Builder装饰的方法// this.doSomeCalculations();// 可以调用// this.doSomeRender();// 参数可以为调用TS方法的返回值// Text(this.calcTextValue())// 6.不允许使用switch语法//switch (expression) {//  case 1://    Text('...')//    break;//  default://    Text('...')//    break;//}// 7.不允许使用表达式// (this.aVar > 10) ? Text('...') : Image('...')// 8. 应避免直接在Text组件内改变count的值// Text(`${this.count++}`)}doSomeCalculations() {}calcTextValue(): string {return 'Hello World';}@Builder doSomeRender() {Text(`Hello World`)}}@Component
struct ChildComponent {build() {// 根节点唯一且必要,可为非容器组件Image('test.jpg')}
}

UI描述需要遵循以下规则:

  1. @Entry装饰的自定义组件,其build()函数下的根节点唯一且必要,且必须为容器组件,其中ForEach禁止作为根节点。 @Component装饰的自定义组件,其build()函数下的根节点唯一且必要,可以为非容器组件,其中ForEach禁止作为根节点。
  2. 不允许声明本地变量
  3. 不允许在UI描述里直接使用console.info,但允许在方法或者函数里使用
  4. 不允许创建本地的作用域
  5. 不允许调用没有用@Builder装饰的方法,允许系统组件的参数是TS方法的返回值
  6. 不允许switch语法,如果需要使用条件判断,请使用if
  7. 不允许使用表达式
  8. 不允许直接改变状态变量

其中,第8点需要注意。在ArkUI状态管理中,状态驱动UI更新。所以,不能在自定义组件的build()或@Builder方法里直接改变状态变量,这可能会造成循环渲染的风险。Text(‘${this.count++}’)在全量更新或最小化更新会产生不同的影响。
build函数中更改应用状态的行为可能会比上面的示例更加隐蔽,如:

  1. 在@Builder,@Extend或@Styles方法内改变状态变量 。
  2. 在计算参数时调用函数中改变应用状态变量,例如 Text(‘${this.calcLabel()}’)。
  3. 对当前数组做出修改,sort()改变了数组this.arr,随后的filter方法会返回一个新的数组。
// 反例
@State arr : Array<...> = [ ... ];
ForEach(this.arr.sort().filter(...), item => { ...
})
// 正确的执行方式为:filter返回一个新数组,后面的sort方法才不会改变原数组this.arr
ForEach(this.arr.filter(...).sort(), item => { ...
})

2.3 页面和自定义组件生命周期

在开始之前,我们先明确自定义组件页面的关系:

  1. 自定义组件:@Component装饰的UI单元,可以组合多个系统组件实现UI的复用,可以调用组件的生命周期。
  2. 页面:即应用的UI页面。可以由一个或者多个自定义组件组成,@Entry装饰的自定义组件为页面的入口组件,即页面的根节点,一个页面有且仅能有一个@Entry。只有被@Entry装饰的组件才可以调用页面的生命周期。

页面生命周期,即被@Entry装饰的组件生命周期,提供以下生命周期接口:

  1. onPageShow页面每次显示时触发一次,包括路由过程、应用进入前台等场景,仅@Entry装饰的自定义组件生效。
  2. onPageHide页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景,仅@Entry装饰的自定义组件生效。
  3. onBackPress当用户点击返回按钮时触发,仅@Entry装饰的自定义组件生效。

组件生命周期,即一般用@Component装饰的自定义组件的生命周期,提供以下生命周期接口:

  1. aboutToAppear组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其build()函数之前执行
  2. aboutToDisappear:aboutToDisappear函数在自定义组件析构销毁之前执行。不允许在aboutToDisappear函数中改变状态变量,特别是@Link变量的修改可能会导致应用程序行为不稳定。

生命周期流程如下图所示,下图展示的是被@Entry装饰的组件(首页)生命周期

 

2.3.1 自定义组件的创建和渲染流程 

  1. 自定义组件的创建:自定义组件的实例由ArkUI框架创建。
  2. 初始化自定义组件的成员变量:通过本地默认值或者构造方法传递参数来初始化自定义组件的成员变量,初始化顺序为成员变量的定义顺序。
  3. 如果开发者定义了aboutToAppear,则执行aboutToAppear方法。
  4. 在首次渲染的时候,执行build方法渲染系统组件,如果子组件为自定义组件,则创建自定义组件的实例。在执行build()函数的过程中,框架会观察每个状态变量的读取状态,将保存两个map:
  1. 状态变量 -> UI组件(包括ForEach和if)
  2. UI组件 -> 此组件的更新函数。即一个lambda方法,作为build()函数的子集,创建对应的UI组件并执行其属性方法,示意如下。
build() {...this.observeComponentCreation(() => {Button.create();})this.observeComponentCreation(() => {Text.create();})...
}

当应用在后台再次启动时,此时应用进程并没有销毁,所以仅需要执行onPageShow。

2.3.2 自定义组件重新渲染


当事件句柄被触发(比如设置了点击事件,即触发点击事件)改变了状态变量时,或者LocalStorage / AppStorage中的属性更改,并导致绑定的状态变量更改其值时:

  1. 框架观察到了变化,将启动重新渲染。
  2. 根据框架持有的两个map(自定义组件的创建和渲染流程中第4步),框架可以知道该状态变量管理了哪些UI组件,以及这些UI组件对应的更新函数。执行这些UI组件的更新函数,实现最小化更新。

2.3.3 自定义组件的删除

  1. 如果if组件的分支改变,或者ForEach循环渲染中数组的个数改变,组件将被删除:在删除组件之前,将调用其aboutToDisappear生命周期函数,标记着该节点将要被销毁。ArkUI的节点删除机制是:后端节点直接从组件树上摘下,后端节点被销毁,对前端节点解引用,前端节点已经没有引用时,将被JS虚拟机垃圾回收。
  2. 自定义组件和它的变量将被删除,如果其有同步的变量,比如@Link、@Prop、@StorageLink,将从同步源上取消注册。

不建议在生命周期aboutToDisappear内使用async await,如果在生命周期的aboutToDisappear使用异步操作(Promise或者回调方法),自定义组件将被保留在Promise的闭包中,直到回调方法被执行完,这个行为阻止了自定义组件的垃圾回收。

以下示例展示了生命周期的调用时机:

// Index.ets
import router from '@ohos.router';@Entry
@Component
struct MyComponent {@State showChild: boolean = true;@State btnColor:string = "#FF007DFF"// 只有被@Entry装饰的组件才可以调用页面的生命周期onPageShow() {console.info('Index onPageShow');}// 只有被@Entry装饰的组件才可以调用页面的生命周期onPageHide() {console.info('Index onPageHide');}// 只有被@Entry装饰的组件才可以调用页面的生命周期onBackPress() {console.info('Index onBackPress');this.btnColor ="#FFEE0606"return true // 返回true表示页面自己处理返回逻辑,不进行页面路由;返回false表示使用默认的路由返回逻辑,不设置返回值按照false处理}// 组件生命周期aboutToAppear() {console.info('MyComponent aboutToAppear');}// 组件生命周期aboutToDisappear() {console.info('MyComponent aboutToDisappear');}build() {Column() {// this.showChild为true,创建Child子组件,执行Child aboutToAppearif (this.showChild) {Child()}// this.showChild为false,删除Child子组件,执行Child aboutToDisappearButton('delete Child').margin(20).backgroundColor(this.btnColor).onClick(() => {this.showChild = false;})// push到page页面,执行onPageHideButton('push to next page').onClick(() => {router.pushUrl({ url: 'pages/page' });})}}
}@Component
struct Child {@State title: string = 'Hello World';// 组件生命周期aboutToDisappear() {console.info('[lifeCycle] Child aboutToDisappear')}// 组件生命周期aboutToAppear() {console.info('[lifeCycle] Child aboutToAppear')}build() {Text(this.title).fontSize(50).margin(20).onClick(() => {this.title = 'Hello ArkUI';})}
}
// page.ets
@Entry
@Component
struct page {@State textColor: Color = Color.Black;@State num: number = 0onPageShow() {this.num = 5}onPageHide() {console.log("page onPageHide");}onBackPress() { // 不设置返回值按照false处理this.textColor = Color.Greythis.num = 0}aboutToAppear() {this.textColor = Color.Blue}build() {Column() {Text(`num 的值为:${this.num}`).fontSize(30).fontWeight(FontWeight.Bold).fontColor(this.textColor).margin(20).onClick(() => {this.num += 5})}.width('100%')}
}

以上示例中,Index页面包含两个自定义组件,一个是被@Entry装饰的MyComponent,也是页面的入口组件,即页面的根节点;一个是Child,是MyComponent的子组件。只有@Entry装饰的节点才可以使页面级别的生命周期方法生效,所以MyComponent中声明了当前Index页面的页面生命周期函数。MyComponent和其子组件Child也同时声明了组件的生命周期函数。

  1. 应用冷启动的初始化流程为:MyComponent aboutToAppear --> MyComponent build --> Child aboutToAppear --> Child build --> Child build执行完毕 --> MyComponent build执行完毕 --> Index onPageShow。
  2. 点击“delete Child”,if绑定的this.showChild变成false,删除Child组件,会执行Child aboutToDisappear方法。
  3. 点击“push to next page”,调用router.pushUrl接口,跳转到另外一个页面,当前Index页面隐藏,执行页面生命周期Index onPageHide。此处调用的是router.pushUrl接口,Index页面被隐藏,并没有销毁,所以只调用onPageHide。跳转到新页面后,执行初始化新页面的生命周期的流程。
  4. 如果调用的是router.replaceUrl,则当前Index页面被销毁,执行的生命周期流程将变为:Index onPageHide --> MyComponent aboutToDisappear --> Child aboutToDisappear。上文已经提到,组件的销毁是从组件树上直接摘下子树,所以先调用父组件的aboutToDisappear,再调用子组件的aboutToDisappear,然后执行初始化新页面的生命周期流程。
  5. 点击返回按钮,触发页面生命周期Index onBackPress,且触发返回一个页面后会导致当前Index页面被销毁。
  6. 最小化应用或者应用进入后台,触发Index onPageHide。当前Index页面没有被销毁,所以并不会执行组件的aboutToDisappear。应用回到前台,执行Index onPageShow。
  7. 退出应用,执行Index onPageHide --> MyComponent aboutToDisappear --> Child aboutToDisappear。

参考文献:
[1]OpenHarmoney应用开发文档 


http://www.ppmy.cn/server/141975.html

相关文章

PG实例CPU使用率高排查思路

一、查看具体哪个会话占用高 top 查看哪个pid使用cpu高 psql 登录到数据库中查看具体的语句 SELECT pid, query FROM pg_stat_activity WHERE pid ‘top查看到的pid’; 二、查看锁 在PostgreSQL中查看锁的状态&#xff0c;你可以使用pg_locks系统视图来获取当前数据库中的锁…

BILSTM法律网站用户提问自动分类

项目源码获取方式见文章末尾&#xff01; 600多个深度学习项目资料&#xff0c;快来加入社群一起学习吧。 《------往期经典推荐------》 项目名称 1.【基于CNN-RNN的影像报告生成】 2.【卫星图像道路检测DeepLabV3Plus模型】 3.【GAN模型实现二次元头像生成】 4.【CNN模型实现…

【网络安全 | 并发问题】Nginx重试机制与幂等性问题分析

未经许可,不得转载。 文章目录 业务背景Nginx的错误重试机制proxy_next_upstream指令配置重试500状态码非幂等请求的重试问题幂等性和非幂等性请求non_idempotent选项的使用解决方案业务背景 在现代互联网应用中,高可用性(HA)是确保系统稳定性的关键要求之一。为了应对服务…

上海ABC行测试面试题回忆版本

11.14号去ABC面试&#xff0c;流程上先做个半个小时的笔试&#xff0c;然后是排队面试。这次做笔试的人很多&#xff0c;有JAVA&#xff0c;大数据&#xff0c;前端&#xff0c;测试&#xff0c;我是最后一批测试。现场没有敢拍照。面试的时候&#xff0c;一共8个面试官&#x…

Snort的配置与使用

声明&#xff1a;学习视频来自b站up主 泷羽sec&#xff0c;如涉及侵权马上删除文章 感谢泷羽sec 团队的教学 视频地址&#xff1a;蓝队基础之网络七层杀伤链_哔哩哔哩_bilibili 目录 一、什么是Snort Snort的主要功能包括&#xff1a; Snort的工作原理&#xff1a; Snort的…

Unity 性能优化方案

‌Unity性能优化的主要方案包括以下几个方面‌&#xff1a; 一、减少Draw Call‌ Draw Call就是CPU调用图形编程接口&#xff0c;是CPU向GPU发送的命令 1.CPU和GPU并行工作的原理 CPU和GPU工作有一个命令缓冲区(Command Buffer) 命令缓冲区包含了一个命令队列&#xff0c;由C…

RDD 算子全面解析:从基础到进阶与面试要点

Spark 的介绍与搭建&#xff1a;从理论到实践_spark环境搭建-CSDN博客 Spark 的Standalone集群环境安装与测试-CSDN博客 PySpark 本地开发环境搭建与实践-CSDN博客 Spark 程序开发与提交&#xff1a;本地与集群模式全解析-CSDN博客 Spark on YARN&#xff1a;Spark集群模式…

PDF24:多功能 PDF 工具使用指南

PDF24&#xff1a;多功能 PDF 工具使用指南 在日常工作和学习中&#xff0c;PDF 是一种常见且重要的文档格式。无论是查看、编辑、合并&#xff0c;还是转换 PDF 文件&#xff0c;能够快速高效地处理 PDF 文档对于提高工作效率至关重要。PDF24 是一款免费、功能全面的 PDF 工具…