HarmonyOS(二)—— 初识ArkTS开发语言(下)之ArkTS声明式语法和组件化基础

前言:

通过前面ArkTS开发语言(上)之TypeScript入门以及ArkTS开发语言(中)之ArkTS的由来和演进俩文我们知道了ArkTS的由来以及演进,知道了ArkTS具备了声明式语法和组件化特性,今天,搭建一个可刷新的排行榜页面。在排行榜页面中,使用循环渲染控制语法来实现列表数据渲染,使用@Builder创建排行列表布局内容,使用装饰器@State、@Prop、@Link来管理组件状态。最后我们点击系统返回按键,来学习自定义组件生命周期函数。

基本概念

首先我们先要明白如下几个基本概念,方便我们更好的理解ArkTS,掌握和理解本demo的知识点。

  1. 声明式UI基本概念

应用界面是由一个个页面组成,ArkTS是由ArkUI框架提供,用于以声明式开发范式开发界面的语言。

声明式UI构建页面的过程,其实是组合组件的过程,声明式UI的思想,主要体现在两个方面:

  • 描述UI的呈现结果,而不关心过程
  • 状态驱动视图更新

类似苹果的SwiftUI中通过组合视图View,安卓Jetpack Compose中通过组合@Composable函数,ArkUI作为HarmonyOS应用开发的UI开发框架,其使用ArkTS语言构建自定义组件,通过组合自定义组件完成页面的构建。

  1. 自定义组件的组成

ArkTS通过struct声明组件名,并通过@Component和@Entry装饰器,来构成一个自定义组件。

  • 使用@Entry和@Component装饰的自定义组件作为页面的入口,会在页面加载时首先进行渲染。
  • 使用@Component装饰的自定义组件,如TitleView这个自定义组件则对应如下内容,作为页面的组成部分。
javascript 复制代码
@Entry
@Component
struct TitleView {...}

在自定义组件内需要使用build方法来进行UI描述。

javascript 复制代码
@Entry
@Component
 struct TitleView
   ...
   build() {
    ...
  } 
}

build方法内可以容纳内置组件和其他自定义组件,如Column和Text都是内置组件,由ArkUI框架提供,TitleView为自定义组件,需要开发者使用ArkTS自行声明。

javascript 复制代码
@Entry
@Component
struct TitleView {
  ...
  build() {
    Column(...) {
      Text(...)
        ...
      ForEach(...{
        TodoItem(...)
      },...)
    }
  ...
  }
}
  1. 组件状态管理装饰器和@Builder装饰器:
    组件状态管理装饰器用来管理组件中的状态,它们分别是:@State、@Prop、@Link。
  • @State装饰的变量是组件内部的状态数据,当这些状态数据被修改时,将会调用所在组件的build方法进行UI刷新。

  • @Prop与@State有相同的语义,但初始化方式不同。@Prop装饰的变量必须使用其父组件提供的@State变量进行初始化,允许组件内部修改@Prop变量,但更改不会通知给父组件,即@Prop属于单向数据绑定。

  • @Link装饰的变量可以和父组件的@State变量建立双向数据绑定,需要注意的是:@Link变量不能在组件内部进行初始化。

  • @Builder装饰的方法用于定义组件的声明式UI描述,在一个自定义组件内快速生成多个布局内容。

@State、@Prop、@Link三者关系如图所示:

  1. 组件生命周期函数
    自定义组件的生命周期函数用于通知用户该自定义组件的生命周期,这些回调函数是私有的,在运行时由开发框架在特定的时间进行调用,不能从应用程序中手动调用这些回调函数。 右图是自定义组件生命周期的简化图示:

后期会专门出一期文章讲解自定义组件的生命周期,敬请期待。

  1. 渲染控制语法:
    渲染控制分if/else:条件渲染和ForEach:循环渲染俩种。
  • if/else:条件渲染
    ArkTS提供了渲染控制的能力。条件渲染可根据应用的不同状态,使用if、else和else if渲染对应状态下的UI内容。

举个列子:

typescript 复制代码
Column() {
   if (this.count > 0) {
       Text('count is positive')
   }
}

条件渲染的具体使用规则,注意事项,以及使用场景见if/else:条件渲染,这里不过过多描述。

  • ForEach:循环渲染
    ForEach接口基于数组类型数据来进行循环渲染,需要与容器组件配合使用,且接口返回的组件应当是允许包含在ForEach父容器组件中的子组件。例如,ListItem组件要求ForEach的父容器组件必须为List组件。

举个列子:

typescript 复制代码
ForEach(
  arr: any[], // 用于迭代的数组
  itemGenerator: (item: any, index?: number) => void, // 生成子组件的lambda函数
  keyGenerator?: (item: any, index?: number) => string // 用于给定数组项生成唯一且稳定的键值
)

条件渲染的具体使用规则,注意事项,以及使用场景见ForEach:循环渲染,这里不过过多描述。只不过ForEach循环渲染相对条件渲染来说相对复杂,后续有空我会专门介绍渲染控制。

了解以上五点,加上前面讲解到的ArkTS开发语言(上)之TypeScript入门初识ArkTS开发语言(中)之ArkTS的由来和演进,我想大家对ArkTS有了基本的认识,也能做出一些简单的功能,下面我就以官方的一个待办列表对初识ArkTS开发语言做一个实战性总结。

ArkTS实战开发

下面我们用ArkTS实战开发一些自定义组件,然后组合实现一个待办列表,最终效果图如下:

我们已经知道,ArkTS通过struct声明组件名,并通过@Component和@Entry装饰器,来构成一个自定义组件。

使用@Entry和@Component装饰的自定义组件作为页面的入口,会在页面加载时首先进行渲染。

typescript 复制代码
@Entry
@Component
struct ToDoList {...}

使用@Component装饰的自定义组件,如ToDoItem这个自定义组件则对应如下内容,作为页面的组成部分。

typescript 复制代码
@Component
struct ToDoItem {...}

在自定义组件内需要使用build方法来进行UI描述。

typescript 复制代码
@Entry
@Component
 struct ToDoList
   ...
   build() {
    ...
  } 
}

build方法内可以容纳内置组件和其他自定义组件,如Column和Text都是内置组件,由ArkUI框架提供,ToDoItem为自定义组件,需要开发者使用ArkTS自行声明。

typescript 复制代码
@Entry
@Component
struct ToDoList {
  ...
  build() {
    Column(...) {
      Text(...)
        ...
      ForEach(...{
        TodoItem(...)
      },...)
    }
  ...
  }
}

1:实战前分析

那么如何实现上面的效果图了,我们对页面组成进行拆分。

会发现,整个页面是有一个标题和若干个ToDoItem组成的,因此我们只需自定义实现标题组件和一个ToDoItem组件即可。

接下来我们就以此分析并实现这俩个组件。

2:实现标题组件

其实,标题组件很简单,就是一个简单的文本展示,我们只需用ArkUI提供的Text实现即可,这里为了加深对家对自定义组件语法和规则使用,我还是简单抽取成一个自定义公共组件吧。

自定义标题组件代码如下:

typescript 复制代码
@Entry
@Component
struct ToDoTitle {
   ...
   build() {
     Row() {
       Column() {
         Text("代办")
         .fontSize(50)
       }
       .width('100%')
     }
     .height('100%')
   }
 }

当然,直接在代办列表页使用系统自带的Text基础组件也可以,如下所示:

typescript 复制代码
@Entry
@Component
struct ToDoList {
   ...
   build() {
     Row() {
       Column() {
          // 直接在使用Text
          Text("代办")
         .fontSize(50)
           ...
       }
       .width('100%')
     }
     .height('100%')
   }
 }

3:分析ToDoItem的组成

ToDoItem相对于标题来说要复杂的多,认真分析其组成,ToDoItem是有一个水平摆放的组件,前面是一个图片,后面是一个普通文本,这是他的外观,同时它具备了一些行为,也就是我们常说的点击事件,在ArkTS中就是指组件状态,这里需要记录ToDoItem的点击的俩种状态:

  • 默认状态:默认情况下这一项高亮显示。
  • 选中状态:这一项背景变成灰色,同时前面图片变种选中状态的图片。

4:ToDoItem组件的实现

下面,我就从ToDoItem组件的外观布局属性配置以及组件状态俩方面来讲解ToDoItem的具体事项

1:配置外观属性与布局

自定义组件的组成使用基础组件和容器组件等内置组件进行组合。但有时内置组件的样式并不能满足我们的需求,ArkTS提供了属性方法用于描述界面的样式。属性方法支持以下使用方式:

  • 常量传递
    例如使用fontSize(50)来配置字体大小。
typescript 复制代码
Text('Hello World')
  .fontSize(50)
  • 变量传递
    在组件内定义了相应的变量后,例如组件内部成员变量size,就可以使用this.size方式使用该变量。
typescript 复制代码
Text('Hello World')
  .fontSize(this.size)
  • 链式调用
    在配置多个属性时,ArkTS提供了链式调用的方式,通过'.'方式连续配置。
typescript 复制代码
Text('Hello World')
  .fontSize(this.size)
  .width(100)
  .height(100)
  • 表达式传递
    属性中还可以传入普通表达式以及三目运算表达式。
typescript 复制代码
Text('Hello World')
  .fontSize(this.size)
  .width(this.count + 100)
  .height(this.count % 2 === 0 ? 100 : 200)
  • 内置枚举类型
    除此之外,ArkTS中还提供了内置枚举类型,如Color,FontWeight等,例如设置fontColor改变字体颜色为红色,并私有fontWeight为加粗。
typescript 复制代码
Text('Hello World')
  .fontSize(this.size)
  .width(this.count + 100)
  .height(this.count % 2 === 0 ? 100 : 200)
  .fontColor(Color.Red)
  .fontWeight(FontWeight.Bold)

对于有多种组件需要进行组合时,容器组件则是描述了这些组件应该如何排列的结果。

ArkUI中的布局容器有很多种,在不同的适用场合选择不同的布局容器实现,ArkTS使用容器组件采用花括号语法,内部放置UI描述。

这里我们将介绍最基础的两个布局------列布局和行布局。

对于如下每一项的布局,两个元素为横向排列,选择Row布局

类似下图所示的布局,整体都是从上往下纵向排列,适用的布局方式是Column列布局。

typescript 复制代码
Column() {
   Text($r('app.string.page_title'))
     ...

   ForEach(this.totalTasks,(item) => {
     TodoItem({content:item})
   },...)
 }
2:记录组件状态的改变

实际开发中由于交互,页面的内容可能需要产生变化,以每一个ToDoItem为例,其在完成时的状态与未完成时的展示效果是不一样的。

声明式UI的特点就是UI是随数据更改而自动刷新的,我们这里定义了一个类型为boolean的变量isComplete,其被@State装饰后,框架内建立了数据和视图之间的绑定,其值的改变影响UI的显示。

typescript 复制代码
@State isComplete : boolean = false;

@State装饰器的作用

用圆圈和对勾这样两个图片,分别来表示该项是否完成,这部分涉及到内容的切换,需要使用条件渲染if / else语法来进行组件的显示与消失,当判断条件为真时,组件为已完成的状态,反之则为未完成。

typescript 复制代码
if (this.isComplete) {
  Image($r('app.media.ic_ok'))
    .objectFit(ImageFit.Contain)
    .width($r('app.float.checkbox_width'))
    .height($r('app.float.checkbox_width'))
    .margin($r('app.float.checkbox_margin'))
} else {
  Image($r('app.media.ic_default'))
    .objectFit(ImageFit.Contain)
    .width($r('app.float.checkbox_width'))
    .height($r('app.float.checkbox_width'))
    .margin($r('app.float.checkbox_margin'))
}

由于两个Image的实现具有大量重复代码,ArkTS提供了@Builder装饰器,来修饰一个函数,快速生成布局内容,从而可以避免重复的UI描述内容。这里使用@Bulider声明了一个labelIcon的函数,参数为url,对应要传给Image的图片路径。

typescript 复制代码
@Builder labelIcon(url) {
  Image(url)
    .objectFit(ImageFit.Contain)
    .width($r('app.float.checkbox_width'))
    .height($r('app.float.checkbox_width'))
    .margin($r('app.float.checkbox_margin'))
}

使用时只需要使用this关键字访问@Builder装饰的函数名,即可快速创建布局。

typescript 复制代码
if (this.isComplete) {
  this.labelIcon($r('app.media.ic_ok'))
} else {
  this.labelIcon($r('app.media.ic_default'))
}

为了让待办项带给用户的体验更符合已完成的效果,给内容的字体也增加了相应的样式变化,这里使用了三目运算符来根据状态变化修改其透明度和文字样式,如opacity是控制透明度,decoration是文字是否有划线。通过isComplete的值来控制其变化。

typescript 复制代码
Text(this.content)
  ...
  .opacity(this.isComplete ? CommonConstants.OPACITY_COMPLETED : CommonConstants.OPACITY_DEFAULT)
  .decoration({ type: this.isComplete ? TextDecorationType.LineThrough : TextDecorationType.None })

最后,为了实现与用户交互的效果,在组件上添加了onClick点击事件,当用户点击该待办项时,数据isComplete的更改就能够触发UI的更新。

typescript 复制代码
@Component
struct ToDoItem {
  @State isComplete : boolean = false;
  @Builder labelIcon(icon) {...}
  ...
  build() {
    Row() {
      if (this.isComplete) {
        this.labelIcon($r('app.media.ic_ok'))
      } else {
        this.labelIcon($r('app.media.ic_default'))
      }
      ... 
    }
    ...
    .onClick(() => {
      this.isComplete= !this.isComplete;
     })
  }
}

5:循环渲染列表数据实现代办列表功能

上面,只是完成了一个ToDoItem组件的开发,当我们有多条待办数据需要显示在页面时,就需要使用到ForEach循环渲染语法。

假设,我们有五条待办数据需要展示在页面上。

typescript 复制代码
total_Tasks:Array<string> = [
  '早起晨练',
  '准备早餐',
  '阅读名著',
  '学习ArkTS',
  '看剧放松'
]

ForEach基本使用中,只需要了解要渲染的数据以及要生成的UI内容两个部分,例如这里要渲染的数组为以上的五条待办事项,要渲染的内容是ToDoItem这个自定义组件,也可以是其他内置组件。

ForEach基本使用如下图所示:

ToDoItem这个自定义组件中,每一个ToDoItem要显示的文本参数content需要外部传入,参数传递使用花括号的形式,用content接受数组内的内容项item。

最终完成的代码及其效果如下。

typescript 复制代码
@Entry
@Component
struct ToDoList {
   ...
   build() {
     Row() {
       Column() {
         Text(...)
           ...
         ForEach(this.totalTasks,(item) => {
           TodoItem({content:item})
         },...)
       }
       .width('100%')
     }
     .height('100%')
   }
 }

ToDoList页面的最终效果图:

总结

现在我们明白了ArkTS声明式开发范式的基本组成,这里我用一张图做一个总结概述

ArkTS声明式开发范式:

  • 装饰器用来装饰类、结构体、方法以及变量,赋予其特殊的含义,如上述示例中 @Entry 、 @Component 、 @State 都是装饰器。具体而言, @Component 表示这是个自定义组件; @Entry 则表示这是个入口组件; @State 表示组件中的状态变量,此状态变化会引起 UI 变更。

  • 自定义组件

    可复用的 UI 单元,可组合其它组件,如上述被 @Component 装饰的 struct Hello。

  • UI 描述

    声明式的方式来描述 UI 的结构,如上述 build() 方法内部的代码块。

  • 内置组件

    框架中默认内置的基础和布局组件,可直接被开发者调用,比如示例中的 Column、Text、Divider、Button。

  • 事件方法

    用于添加组件对事件的响应逻辑,统一通过事件方法进行设置,如跟随在Button后面的onClick()。

  • 属性方法

    用于组件属性的配置,统一通过属性方法进行设置,如fontSize()、width()、height()、color() 等,可通过链式调用的方式设置多项属性。

从UI框架的需求角度,ArkTS在TS的类型系统的基础上,做了进一步的扩展:定义了各种装饰器、自定义组件和UI描述机制,再配合UI开发框架中的UI内置组件、事件方法、属性方法等共同构成了应用开发的主体。在应用开发中,除了UI的结构化描述之外,还有一个重要的方面:状态管理。如上述示例中,用 @State 装饰过的变量 myText ,包含了一个基础的状态管理机制,即 myText 的值的变化会自动触发相应的 UI 变更 (Text组件)。ArkUI 中进一步提供了多维度的状态管理机制。和 UI 相关联的数据,不仅可以在组件内使用,还可以在不同组件层级间传递,比如父子组件之间,爷孙组件之间,也可以是全局范围内的传递,还可以是跨设备传递。另外,从数据的传递形式来看,可分为只读的单向传递和可变更的双向传递。开发者可以灵活的利用这些能力来实现数据和 UI 的联动。

总体而言,ArkUI开发框架通过扩展成熟语言、结合语法糖或者语言原生的元编程能力、以及UI组件、状态管理等方面设计了统一的UI开发范式,结合原生语言能力共同完成应用开发。这些构成了当前ArkTS基于TS的主要扩展。

ArkUI完整的开发范式可参考[方舟开发框架概述]。(https://developer.harmonyos.com/cn/docs/documentation/doc-guides/arkui-overview-0000001281480754)

相关推荐
远望清一色3 分钟前
基于MATLAB边缘检测博文
开发语言·算法·matlab
何曾参静谧11 分钟前
「Py」Python基础篇 之 Python都可以做哪些自动化?
开发语言·python·自动化
Prejudices15 分钟前
C++如何调用Python脚本
开发语言·c++·python
我狠狠地刷刷刷刷刷28 分钟前
中文分词模拟器
开发语言·python·算法
wyh要好好学习31 分钟前
C# WPF 记录DataGrid的表头顺序,下次打开界面时应用到表格中
开发语言·c#·wpf
AitTech32 分钟前
C#实现:电脑系统信息的全面获取与监控
开发语言·c#
qing_04060334 分钟前
C++——多态
开发语言·c++·多态
孙同学_34 分钟前
【C++】—掌握STL vector 类:“Vector简介:动态数组的高效应用”
开发语言·c++
froginwe1135 分钟前
XML 编辑器:功能、选择与使用技巧
开发语言
Jam-Young41 分钟前
Python的装饰器
开发语言·python