[鸿蒙开发] 5 - ArkTS入门学习

387 阅读9分钟

前言:本来想跟着官方文档仔细学一遍的,但是由于报名了北京鸿蒙的线下培训,要求先看完基础在线课程,所以只能先观看视频,然后反过来再仔细了解每个功能点。

1. ArkTS介绍

ArkTS是HarmonyOS优选的主力应用开发语言,是TypeScript的扩展:

  • 基本语法:ArkTS定义了声明式UI描述、自定义组件和动态扩展UI元素的能力;
  • 状态管理:提供了多维度的状态管理机制;
  • 渲染控制:提供了渲染控制的能力,根据应用的不同状态,渲染UI内容;

ArkTS、TypeScript、JavaScript的关系:

截屏2024-03-17 20.47.10.png

2.TypeScript快速入门

如果已经学会TS了,可直接跳过此部分。

2.1 基础类型

布尔值:
let isDone: boolean = false

// 数字:
let decLiteral: number = 2023
let binaryLiteral: number = 0b111
let octalLiteral: number = 0o3747
let hexLiteral: number = 0x7e7

// 字符串
let name:string = "hello"

// 数组
let list1: number[] = [1,2,3]
let list2: Array<number> = [1,2,3]

// 元组
let x: [string, number]
x = ['hello', 10]

// 枚举,对JS标准数据类型的补充
enum Color {Red, Green, Blue}

// unknown,指定一个任意类型
let notSure: unknown = 4
notSure = false

// void 函数没有返回值
function test(): void {

}

// null 和undefined
TS中它俩各自有自己的类型:
let u: undefined = undefined
let n: null = null

// 联合类型,表示取值可以为多种类型的一种
let myFavoriteNumber: string | number
myFavoriteNumber = "seven"
myFavoriteNumber = 7

2.2 条件语句

let num: number = 5

// if
if (num > 0) {
    console.log("xxx")
}

// if...else
if (num > 0) {
    console.log("xxx")
} else {
    console.log("xxx")
}

// if...else if...else
if (num > 0) {
    console.log("xxx")
} else if (num < 0) {
    console.log("xxx")
} else {
    console.log("xxx")
}

// 使用switch:
switch (num) {
    case 1: {
        console.log(1)
        break
    }
    case 2: {
        console.log(2)
        break
    }
    default: {
        console.log(num)
        break
    }
}

2.3 函数

// 有名函数
function add(x: number, y: number): number {
    return x+y
}

// 匿名函数
let myAdd = function (x: number, y: number): number {
    return x + y
}

// 函数的可选参数
function buildNmae(firstName: string, lastName?: string): string {
    if (lastName) {
        return firstName + lastName
    } else {
        return firstname
    }
}
buildName('Bob')
buildName('Bob', 'Adams')

// 函数的剩余参数,数量不限
function getEmployeeName(firstName: string, ...restOfName: string[]): string {
    return firstName + restOfName.join('')
}
getEmployeeName('Tom')
getEmployeeName('Tom', 'Sandy', 'Lucas')

// 箭头函数,匿名函数的简写语法,省略了function关键字
([param1, param2, ...paramn]) => {
    ...
}

let arrowFun = ([param1, param2, ...paramn]) => {
    // ...
}

// 正常的函数
function testNumber(num: number) {
    if (num > 0) {
        console.log("> 0")
    } else {
        console.log("< 0")
    }
}

testNumber(1)

// 箭头函数
let arrowFun = (num: number) => {
    if (num > 0) {
        console.log("> 0")
    } else {
        console.log("< 0")
    }
}

arrowFun(1)

2.4 类

// 基本使用
class Person {
    private name: string
    private age: number
    
    constructor(name: string, age: number) {
        this.name = name
        this.age = age
    }
    
    public getPersonInfo(): string {
        return this.name + this.age
    }

}

let person = new Person('jack', 18)
person.getpersonInfo()

// 继承
class Employee extends Person {
    private department: string
    
    constructor(name: string, age: number, department: string) {
        this.name = name
        this.number = number
        this.department = department
    }

    public getEmployeeInfo(): string {
        return this.getpersonInfo + this.department
    }
}

2.5 模块

export和import

// 导出一个类
export class NewsData {
    title: string
    
    constructor(title: string) {
        this.title = title
    }
}

// 导入一个类
import { NewsData } from '../common/NewsData';

2.6 迭代器

当一个对象实现了Symbol:iterator属性时,我们认为它是可迭代的。内置的类型比如:Array Map Set String等

let array = [3,4,5]

// for...of
for (let number of array) {
    console.log(number) // 3 4 5
}

// for...in 对数组的下标进行遍历
for (let i in array) {
    console.log(i) // 0 1 2
}

3.ArkTS基本语法

3.1 基本语法概述

下面是一个示例,当点击按钮时,文本内容会从Hello World编程Hello ArkUI:

image.png

示例中的代码如下所示:

image.png

这个代码主要分为6个部分:

  • 装饰器:用于装饰类、结构、方法和变量,赋予特殊的含义;
    • @Component表示自定义组件;
    • @Entry表示该自定义组件为入口组件;
    • @State表示组件中的状态变量,状态变量变化会触发UI刷新;
  • UI描述:以声明式的方式来描述UI的结构,比如build()方法中的代码块;
  • 自定义组件:可复用的UI单元,可组合其他组件,比如上面被@Component装饰的struct hello,就是一个自定义组件;
  • 系统组件:ArkUI默认内置的基础组件(Text Button)和容器组件(Column),可直接使用;
  • 属性方法:通过链式调用配置多项属性,比如文字大小、宽度、高度等;
  • 事件方法:通过链式调用设置多个事件的响应逻辑;

3.2 声明式UI描述

ArkTS以声明方式组合和扩展组件来描述应用程序的UI,同时还提供了基本的属性、事件和子组件配置方法。

创建组件:

Column() {
  Text('item 1')
  Divider()
  Text('item 2')
  // $r形式引入应用资源,可应用于多语言场景
  Text($r('app.string.title_value'))
  Text(`count: ${this.count}`)
}

配置属性:

Text('hello')
  .fontSize(this.size)
Image('test.jpg')
  .width(this.count % 2 === 0 ? 100 : 200)    
  .height(this.offset + 100)

// 对于属性有枚举类型可直接使用
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))
  
// 使用声明的箭头函数,可以直接调用
fn = () => {
  console.info(`counter: ${this.counter}`)
  this.counter++
}
...
Button('add counter')
  .onClick(this.fn)

配置子组件:

// 在尾随闭包{...}中添加子组件的UI描述:
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 自定义组件

3.3.1 基本用法

// 装饰器,用于表示构建一个自定义组件
@Component
struct HelloComponent {
  @State message: string = 'Hello, World!';

  build() {
    // HelloComponent自定义组件组合系统组件Row和Text
    Row() {
      Text(this.message)
        .onClick(() => {
          // 状态变量message的改变驱动UI刷新,UI从'Hello, World!'刷新为'Hello, ArkUI!'
          this.message = 'Hello, ArkUI!';
        })
    }
  }
}

如果在另外的文件中引用该自定义组件,需要使用export关键字导出,在使用的页面import导入:

截屏2024-03-17 22.04.55.png

HelloComponent可以在其他自定义组件中的build()函数中多次创建,实现自定义组件的重用:

@Entry  // 表示该组件为入口组件
@Component
struct ParentComponent {
  build() {
    Column() {
      Text('ArkUI message')
      HelloComponent({ message: 'Hello, World!' });
      Divider()
      HelloComponent({ message: '你好!' });
    }
  }
}

3.3.2 自定义组件的基本结构

// 表示该自定义组件将作为UI页面的入口,单个UI页面中,最多可以使用@Entry装饰一个自定义组件
@Entry
// 装饰器,用于表示构建一个自定义组件,只能用于装饰struct
@Component
// 使用struct + 自定义组件名 + { ... },不能继承
struct HelloComponent { 
  @State message: string = 'Hello, World!';

  // build()函数用于定义自定义组件的声明式UI描述,自定义组件必须定义build函数
  build() {
    // HelloComponent自定义组件组合系统组件Row和Text
    Row() {
      Text(this.message)
        .onClick(() => {
          // 状态变量message的改变驱动UI刷新,UI从'Hello, World!'刷新为'Hello, ArkUI!'
          this.message = 'Hello, ArkUI!';
        })
    }
  }
}

3.3.3 自定义组件的参数规定

在创建自定义组件的过程中,根据装饰器的规则来初始化自定义组件的参数:

@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函数中的语言称为UI描述,遵循以下规则:

  • @Entry装饰的自定义组件,build函数下的根节点必须是容器组件;
  • @Component装饰的自定义组件,build函数的根节点可以为非容器组件;
  • ForEach禁止作为根节点;
@Entry
@Component
struct MyComponent {
  build() {
    // 根节点唯一且必要,必须为容器组件
    Row() {
      ChildComponent() 
    }
  }
}

@Component
struct ChildComponent {
  build() {
    // 根节点唯一且必要,可为非容器组件
    Image('test.jpg')
  }
}

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

  • 自定义组件:@Component装饰的UI单元,可以调用组件的生命周期;
  • 页面:应用的UI页面,@Entry装饰的自定义组件为页面的入口组件,即页面的根节点,只有被@Entry装饰的组件才可以调用页面的生命周期;

页面生命周期方法如下:

  • onPageShow:页面展示,包括路由过程、应用进入前台等
  • onPageHide:页面消失,包括路由过程、应用进入后台等
  • onBackPress:用户点击返回按钮时出发

组件生命周期方法如下:

  • aboutToAppear:组件即将出现时回调。具体是在创建自定义组件的新实例后,在执行其build之前,可以进行数据的初始化操作;
  • aboutToDisappear:在自定义组件销毁前执行,可以释放一些资源;

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

image.png

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

  • 自定义组件的创建:自定义组件的实例由ArkUI框架创建;
  • 初始化自定义组件的成员变量:通过本地的默认值或者构造方法传递参数来初始化自定义组件的成员变量,初始顺序为成员变量的定义顺序;
  • 如果开发者定义了aboutToAppear,则执行aboutToAppear方法
  • 首次渲染的时候,执行build方法渲染系统组件,如果子组件为自定义组件,则创建自定义组件的实例。在执行build函数的过程中,框架会观察每个状态变量的读取状态,保存两个map:
    • 状态变量:UI组件
    • UI组件:此组件的更新函数

当应用在后台启动时,此时应用进程没有销毁,所以只会执行onPageshow。

3.3.6 自定义组件重新渲染

当事件触发时,改变了状态变量时,或者LocalStorage AppStorage中的属性更改,并导致绑定的状态变量更改其值时:

  • 框架观察到了变化,将启动重新渲染;
  • 根据框架持有的两个map,框架可以知道该状态变量管理哪些UI组件,以及这些UI组件对应的更新函数,执行这些UI组件的更新函数,实现最小化更新;

3.3.7 自定义组件的删除

  • 删除组件之前会调用其aboutToDisappear生命周期函数,标记该节点将要被销毁。ArkUI的节点删除机制是:后端节点直接从组件树上摘下,后端节点被销毁,对前端节点解引用,前端节点已经没有引用时,将被JS虚拟机垃圾回收;
  • 自定义组件和它的变量将被删除,如果有同步的变量将从同步源上取消注册;
// Index.ets
import router from '@ohos.router';

@Entry
@Component
struct MyComponent {
  @State showChild: boolean = true;

  // 只有被@Entry装饰的组件才可以调用页面的生命周期
  onPageShow() {
    console.info('Index onPageShow');
  }
  // 只有被@Entry装饰的组件才可以调用页面的生命周期
  onPageHide() {
    console.info('Index onPageHide');
  }

  // 只有被@Entry装饰的组件才可以调用页面的生命周期
  onBackPress() {
    console.info('Index onBackPress');
  }

  // 组件生命周期
  aboutToAppear() {
    console.info('MyComponent aboutToAppear');
  }

  // 组件生命周期
  aboutToDisappear() {
    console.info('MyComponent aboutToDisappear');
  }

  build() {
    Column() {
      // this.showChild为true,创建Child子组件,执行Child aboutToAppear
      if (this.showChild) {
        Child()
      }
      // this.showChild为false,删除Child子组件,执行Child aboutToDisappear
      Button('delete Child').onClick(() => {
        this.showChild = false;
      })
      // push到Page2页面,执行onPageHide
      Button('push to next page')
        .onClick(() => {
          router.pushUrl({ url: 'pages/Page2' });
        })
    }

  }
}

@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).onClick(() => {
      this.title = 'Hello ArkUI';
    })
  }
}