鸿蒙开发基础入门

一、熟悉目录结构


二、ArkTS语法介绍

  • ArkTS是为构建高性能应用设计的编程语言,语法继承TypeScript,并进行了优化,拥有更强的类型约束
  • ArkTS提供了声明式UI范式,符合移动开发的最新趋势
  • ArkTS摒弃了部分影响运行时的性能的语法,比如Any。取而代之的是显式类型定义或类型推断
  • ArkTS提供了更强的并发编程能力
  • ArkTS兼容JS/TS三方库

强的类型约束

  • ArkTS要求所有类型在程序运行前都是已知的。避免运行时类型检查。
typescript 复制代码
// ArkTS
const peter: Person = { // 明确类型
  id: "001",  name: "Peter",  age: 10, gender: "male"
}

ArkTS在运行时不允许更改对象布局。

typescript 复制代码
// TypeScript
class Person {
  id: string = ""
  name: string = ""
}

let peter = new Person()
(peter as any).desc = "XX"
delete (peter as any).desc
typescript 复制代码
// ArkTS
class Person {
  id: string = ""
  name: string = ""
  desc?: string
}

let peter = new Person()
peter.desc = "XX" // 可选属性赋值
peter.desc = undefined // 置空可选属性

声明式UI

  • UI描述:装饰器、自定义组件和UI描述机制
  • 状态管理:数据驱动UI,数据可在组件、页面、应用及跨设备传递

变量

typescript 复制代码
// 声明变量
let count: number = 0
count = 40

// 声明常量
const MAX_COUNT: number = 100
MAX_COUNT = 200 // 报错,不能修改const声明的值

类型

基本类型:string,number,boolean,enum

引用类型:Array,自定义类

联合类型:Union

类型别名:Aliases

基本类型 string,number,boolean,enum
typescript 复制代码
let name: string = "ZhangSan"
let age: number = 20
let isMale: boolean = true

enum Color {
  Red,
  Blue,
  Green
}

let bgColor: Color = Color.Red
引用类型:Array,自定义类
typescript 复制代码
let students: Array<string> = ["ZhangSan", "LiSi", "WangWu"]
let students: string[] = ["ZhangSan", "LiSi", "WangWu"]

class Person {...}
let person: Person = new Person()
联合类型:Union 允许变量的值为多个类型
typescript 复制代码
let id: number | string = 1
id = "001"
类型别名:Aliases 允许给一个类型取别名,方便理解和复用
typescript 复制代码
type Matrix = number[ ][ ]
type NullableObject = Object | null

空安全

基于联合类型,可以声明可空变量。

typescript 复制代码
let name: string | null = null
console.log(name.length.toString()) // 报错,变量可能为null,无法获取null的length

但以上声明使用时不加判断,会报错,因为编译器认为name有可能为null。null无法调用length属性。

typescript 复制代码
if (name != null) { ... } // 1. 用if/else判断
const unwrapped = name ?? "" // 2. 空值合并表达式,??左边为空时返回??右边的值
let len = name?.length // 3. ?可选链,如果name为null,运算符返回undefined

有多种方法使用可空变量,if/else判断、空值合并表达式或者可选链。

类型判断与类型推断

ArkTS是类型安全的语言,编译器会进行类型检查

typescript 复制代码
let name: string = "ZhangSan"
name = 20 // 报错,number不能赋值给string类型变量

以上代码中,name已经为string类型,不能赋值number类型。

ArkTS可以省略类型声明,此时会自动推导类型

typescript 复制代码
let age = 20 // age自动推断为number类型

age没有声明类型,编译器自动推导为number型。

语句

条件语句及条件表达式

条件语句:根据条件真值(true或false)执行不同代码块

typescript 复制代码
let score: number = 90
let passed: boolean = false
if (score >= 60) {
  passed = true
} else {
  passed = false
}

条件表达式:条件 ? 为真返回值 : 为假返回值

typescript 复制代码
let score: number = 90
let passed: boolean = score >= 60 ? true : false
循环语句

用于重复执行的语句。有for循环、for...of循环以及while循环

给定一个数组

typescript 复制代码
let students: string[] = ["ZhangSan", "LiSi", "WangWu"]

for循环

typescript 复制代码
for (let i = 0; i < students.length; i++) {
  console.log(students[i])
}

for...of循环

typescript 复制代码
for (let student of students) {
  console.log(student)
}

while循环

typescript 复制代码
let index = 0
while (index < students.length) {
  console.log(students[index])
  index++
}

函数的声明和使用

函数是多条语句的组合,组成一个可重用的代码块。

用function声明函数

格式如下

function 函数名(参数1: 参数类型1, 参数2: 参数类型2): 返回类型 {

// 函数体

}

例:

typescript 复制代码
function printStudentInfo(students: string[]): void {
  for (let student of students) {
    console.log(student)
  }
}

printStudentInfo(["ZhangSan", "LiSi", "WangWu"])

箭头函数/lambda

简化声明,匿名函数。通常用于把函数作为参数传递。格式如下

(参数1: 参数类型1, 参数2: 参数类型2): 返回类型 => { // 函数体 }

例:

typescript 复制代码
(name: string): void => { console.log(name) }

上例中,返回类型void可以省略。如果函数体只有一行、整个函数声明只有一行,可以省略花括号。

闭包函数

一个函数可以作为另一个函数的返回值。

typescript 复制代码
type returnType = () => string
function outerFunc(): () => string {
  let count = 0
  return (): string => {
    count++
    return count.toString()
  }
}

let invoker = outerFunc()
console.log(invoker()) // 输出1
console.log(invoker()) // 输出2

类的声明和使用

ArkTS支持面向对象编程。可声明对象属性和方法。

typescript 复制代码
class Person {
  name: string = "ZhangSan"
  age: number = 20
  isMale: boolean = true
}

创建类的实例

typescript 复制代码
const person = new Person()
console.log(person.name)

const person: Person = {
  name: "ZhangSan",
  age: 29,
  isMale: true
}
console.log(person.name)

构造器:在创建类实例的时候可以执行一些代码或传参。

typescript 复制代码
class Person {
  name: string = "ZhangSan"
  age: number = 20
  isMale: boolean = true
  constructor(
    name: string,
    age: number,
    isMale: boolean
  ) {
    this.name = name
    this.age = age
    this.isMale = isMale
  }
}

const person = new Person("ZhangSan", 20, false)
console.log(person.name)

上例中在Person声明时定义了一个constructor构造器,创建实例的时候传入相应参数,会执行构造器里的语句。

实例方法:实例调用的方法

typescript 复制代码
class Person {
  name: string = "ZhangSan"
  age: number = 20
  isMale: boolean = true
  
  constructor(...){...}
  
  printInfo() {
    if (this.isMale) {
      console.log(`${this.name} is a boy, he is ${this.age} years old`)
    } else {
      console.log(`${this.name} is a girl, she is ${this.age} years old`)
    }
  }
}

const person = new Person("LiSi", 18, false)
person.printInfo()

上例中声明了一个实例方法printInfo()。在实例person中被调用。

可见性及属性

类成员有 public、private、protected可见性。

public修饰符表示这是公开成员,外部可以自由访问。

private修饰符表示私有成员,只能用在当前类的内部,类的实例和子类都不能使用该成员。

protected修饰符表示该成员是保护成员,只能在类的内部使用该成员,实例无法使用该成员,但是子类内部可以使用。

typescript 复制代码
class Person {
  public name: string = "ZhangSan"
  private _age: number = 20
  isMale: boolean = true

  constructor(...) {...}

  printInfo() {...}

  get age(): number {
    return this._age
  }
  set age(age: number) {
    this._age = age
  }
}

const person: Person = new Person("LiSi", 18, true)
console.log(person._age.toString()) // 无法访问
console.log(person.age.toString())

另外,对于上例中private _age可以声明setter和getter,在调用的时候可以与访问属性的语法一样来访问。

继承

子类继承父类的特征和行为。关键字extends。

typescript 复制代码
class Employee extends Person {
  department: string
  
  constructor(
    name: string,
    age: number,
    isMale: boolean,
    department: string
  ) {
    super(name, age, isMale)
    this.department = department
  }
}

const employee: Employee = new Employee("LiSi", 18, false, "XCompany")
employee.printInfo()

上例中的Employee继承了Person。增加了department属性。构造函数重写了父类的构造函数。使用super()方法可以调用父类的方法实现。

多态

子类继承父类,可以重写父类方法

typescript 复制代码
class Employee extends Person {
  department: string

  constructor(...) {...}

  printInfo(): void {
    super.printInfo()
    console.log(`working in ${this.department}}`)
  }
}

const person: Person = new Person("LiSi", 18, false)
person.printInfo()

const employee: Employee = new Employee("LiSi", 18, false, "XCompany")
employee.printInfo()

Employee构造函数重写了父类的构造函数。使用super()方法可以调用父类的方法实现。在Employee实例化的时候调用重写的构造函数。另外printInfo()也重写了父类的对应方法,并用super()调用了父类的方法。在调用Employee的printInfo()方法时会先调用父类的方法,再执行之后的代码。

模块导出与导入

ArkTS中文件内的作用域是独立的。如果想要在B文件中引用A文件定义的变量、函数、类等,需要使用export关键字。

typescript 复制代码
// Person.ets
export class Person {...}
typescript 复制代码
// Index.ets
import { Person } from './Person'

const person = new Person("LiSi", 18, false)
person.printInfo()

声明式UI

声明式UI是当前移动开发的UI语法新趋势。它相较于传统的命令式UI,代码结构更直观。拥有声明式布局描述及状态驱动视图更新的特点。

传统的命令式UI代码,我们拿安卓举例,像这样

java 复制代码
TextView title = findViewById(R.id.tv_title);
title.setText("HarmonyOS Developer World") ;
Button button = findViewById(R.id.btn_join);
Button.setText("Join Now");

LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(300,300);
child.setLayoutParams(layoutParams);
linear_layout.addView(title, Integer.valueOf(add_index) - 1);
linear_layout.addView(button, Integer.valueOf(add_index) - 1);

而如果使用声明式UI,可以写成这样

typescript 复制代码
Column() { 
	Text("HarmonyOS Developer World") 
	Button("Join Now") 
}

本章将以一个待办事项界面为例讲解ArkTS声明式UI。

声明式描述

布局描述

下例中描述了一个横向布局,里面依次排列了一张图片和一个文本框,对应的UI组件如图:

属性设置

使用点语法设置组件属性

typescript 复制代码
Text("待办")
  .fontSize(28)
  .fontWeight(FontWeight.Bold)

渲染控制

支持if、else条件渲染。

typescript 复制代码
Column() {
  Text(`count=${this.count}`)
  if (this.count > 0) {
    Text(`count is positive`)
      .fontColor(Color.Green)
  }
}

支持ForEach循环

typescript 复制代码
Column() {
  ForEach(this.simpleList, (item: string) => {
    Text(item)
  }, (item: string) => item)
}

支持LazyForEach循环

typescript 复制代码
private data: MyDataSource = new MyDataSource()
...
List({ space: 3 }) {
  LazyForEach(this.data, (item: string) => {
    ListItem() {
      Row() {
        Text(item)
         .fontSize(50)
      }
    }
  }, (item: string) => item)
}.cachedCount(5)

class MyDataSource extends BasicDataSource {
  private dataArray: string[] = [];

  public totalCount(): number {
    return this.dataArray.length;
  }

  public getData(index: number): string {
    return this.dataArray[index];
  }

  public addData(index: number, data: string): void {
    this.dataArray.splice(index, 0, data);
    this.notifyDataAdd(index);
  }

  public pushData(data: string): void {
    this.dataArray.push(data);
    this.notifyDataAdd(this.dataArray.length - 1);
  }
}

// Basic implementation of IDataSource to handle data listener
class BasicDataSource implements IDataSource {
  private listeners: DataChangeListener[] = [];
  private originDataArray: string[] = [];

  public totalCount(): number {
    return 0;
  }

  public getData(index: number): string {
    return this.originDataArray[index];
  }

  // 该方法为框架侧调用,为LazyForEach组件向其数据源处添加listener监听
  registerDataChangeListener(listener: DataChangeListener): void {
    if (this.listeners.indexOf(listener) < 0) {
      console.info('add listener');
      this.listeners.push(listener);
    }
  }

  // 该方法为框架侧调用,为对应的LazyForEach组件在数据源处去除listener监听
  unregisterDataChangeListener(listener: DataChangeListener): void {
    const pos = this.listeners.indexOf(listener);
    if (pos >= 0) {
      console.info('remove listener');
      this.listeners.splice(pos, 1);
    }
  }

  // 通知LazyForEach组件需要重载所有子组件
  notifyDataReload(): void {
    this.listeners.forEach(listener => {
      listener.onDataReloaded();
    })
  }

  // 通知LazyForEach组件需要在index对应索引处添加子组件
  notifyDataAdd(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataAdd(index);
    })
  }

  // 通知LazyForEach组件在index对应索引处数据有变化,需要重建该子组件
  notifyDataChange(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataChange(index);
    })
  }

  // 通知LazyForEach组件需要在index对应索引处删除该子组件
  notifyDataDelete(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataDelete(index);
    })
  }

  // 通知LazyForEach组件将from索引和to索引处的子组件进行交换
  notifyDataMove(from: number, to: number): void {
    this.listeners.forEach(listener => {
      listener.onDataMove(from, to);
    })
  }
}

状态驱动UI更新

使用@State @Prop @Link等驱动UI更新。

下例说明了一个状态属性是如何驱动UI更新的,isComplete选择不同的图片资源。

下图说明了isComplete控制了文字的样式

以下是其他状态装饰器

@State:@State装饰的变量拥有其所属组件的状态,可以作为其子组件单向和双向同步的数据源。当其数值改变时,会引起相关组件的渲染刷新。

@Prop:@Prop装饰的变量可以和父组件建立单向同步关系,@Prop装饰的变量是可变的,但修改不会同步回父组件。

@Link:@Link装饰的变量和父组件构建双向同步关系的状态变量,父组件会接受来自@Link装饰的变量的修改的同步,父组件的更新也会同步给@Link装饰的变量。

@Provide/@Consume:@Provide/@Consume装饰的变量用于跨组件层级(多层组件)同步状态变量,可以不需要通过参数命名机制传递,通过alias(别名)或者属性名绑定。

@Observed:@Observed装饰class,需要观察多层嵌套场景的class需要被@Observed装饰。单独使用@Observed没有任何作用,需要和@ObjectLink、@Prop连用。

@ObjectLink:@ObjectLink装饰的变量接收@Observed装饰的class的实例,应用于观察多层嵌套场景,和父组件的数据源构建双向同步。

自定义组件

当界面中有相同结构的模块时,可以封装成一个独立组件。

比如每一行代办事项都是由一张图片和一个文本组成。

自定义组件用@Component装饰器来声明。ToDoItem表示每一行代办事项。

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

在首页如图所示来使用。

子组件的数据

复用的子组件,需要传入数据来显示不同内容,本例中通过content属性给ToDoItem传递数据。

typescript 复制代码
@Component
export default struct ToDoItem {
  private content?: string
  build() {
    Row() {
      Image($r('app.media.ic_default'))
      Text(this.content)
    }
  }
}

在首页中参考如下代码传递数据

typescript 复制代码
@Entry
@Component
struct ToDoListPage {
  build() {
    Column({ space: 16 }) {
      Text("待办")
      ...
      ToDoItem({ content: "学习" })
    }
  }
}

组件的生命周期

每个组件,包括页面组件和子组件,都有相应的生命周期。生命周期如下图

自定义组件和页面的关系:

•自定义组件:@Component装饰的UI单元,可以组合多个系统组件实现UI的复用,可以调用组件的生命周期。

•页面:即应用的UI页面。可以由一个或者多个自定义组件组成,@Entry装饰的自定义组件为页面的入口组件,即页面的根节点,一个页面有且仅能有一个@Entry。只有被@Entry装饰的组件才可以调用页面的生命周期。

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

onPageShow:页面每次显示时触发一次,包括路由过程、应用进入前台等场景。

onPageHide:页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景。

onBackPress:当用户点击返回按钮时触发。

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

aboutToAppear:组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其build()函数之前执行。

aboutToDisappear:aboutToDisappear函数在自定义组件析构销毁之前执行。不允许在aboutToDisappear函数中改变状态变量,特别是@Link变量的修改可能会导致应用程序行为不稳定。

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

@Builder装饰器:自定义构建函数

区别与子组件的声明,在当前组件中也可以使用@Builder来装饰一个方法,用于提取一些可复用的UI代码。

在ToDoItem中,对于Image组件的描述可以提取成一个方法,并用@Builder来装饰,然后可以根据条件引用不同图片文件。

typescript 复制代码
@Component
export default struct ToDoItem {
  private content?: string

  @Builder
  labelIcon(icon: Resource) {
    Image(icon)
      .objectFit(ImageFit.Contain)
      .width(28)
      .height(28)
      .margin(20)
  }
  build() {
    Row() {
      this.labelIcon($r('app.media.ic_default'))
      Text()
    }
  }
}

渲染列表数据

子组件ToDoItem的设计已经完成,现在在首页,我们需要用一个属性提供列表数据。下例中totalTasks保存了一个字符串数组。用ForEach来循环渲染。

typescript 复制代码
@Entry
@Component
struct ToDoListPage {
  private totalTasks: Array<string> = [
    "晨练",
    "做早餐",
    "读书",
    "学习",
    "刷剧"
  ]

  build() {
    Column({ space: 16 }) {
      Text("待办")
      ...
      ForEach(this.totalTasks, (item: string) => {
        ToDoItem({ content: item })
      }, ...)
    }
  }
}
相关推荐
枫叶丹44 小时前
【HarmonyOS之旅】HarmonyOS开发基础知识(三)
华为od·华为·华为云·harmonyos
SoraLuna9 小时前
「Mac畅玩鸿蒙与硬件47」UI互动应用篇24 - 虚拟音乐控制台
开发语言·macos·ui·华为·harmonyos
AORO_BEIDOU13 小时前
单北斗+鸿蒙系统+国产芯片,遨游防爆手机自主可控“三保险”
华为·智能手机·harmonyos
博览鸿蒙14 小时前
鸿蒙操作系统(HarmonyOS)的应用开发入门
华为·harmonyos
Damon小智21 小时前
HarmonyOS NEXT 技术实践-基于基础视觉服务的多目标识别
华为·harmonyos
匹马夕阳1 天前
华为笔记本之糟糕的体验
华为·笔记本电脑
egekm_sefg1 天前
华为、华三交换机纯Web下如何创关键VLANIF、操作STP参数
网络·华为
岳不谢2 天前
华为DHCP高级配置学习笔记
网络·笔记·网络协议·学习·华为
爱笑的眼睛112 天前
uniapp 极速上手鸿蒙开发
华为·uni-app·harmonyos
K.P2 天前
鸿蒙元服务从0到上架【第三篇】(第二招有捷径)
华为·harmonyos·鸿蒙系统