ArkTS是HarmonyOS主力应用开发语言。它在TypeScript(简称TS)的基础上,匹配ArkUI框架,扩展了声明式UI、状态管理等相应的能力,让开发者以更简洁、更自然的方式开发跨端应用。
let isDone: boolean = false;
TypeScript里的所有数字都是浮点数,这些浮点数的类型是number。除了支持十进制,还支持二进制、八进制、十六进制
let decLiteral: number = 2023;
let binaryLiteral: number = 0b11111100111;
let octalLiteral: number = 0o3747;
let hexLiteral: number = 0x7e7;
console.log('decLiteral is' + decLiteral)
// 结果都是2023
let name: string = "Jacky";
TypeSscript支持两种方式声明数组:
let list1: number[] = [1, 2, 3];
let list2: Array = [1, 2, 3];
let x: [string, number];
x = ['hello', 10]
enum Color {Red, Green, Blue};
let c: Color = Color.Green;
有时候,我们会想要为那些在编程阶段还不清楚类型的变量指定一个类型。那么我们可以使用 unknown 类型来标记这些类型
let notSure: unknown = 4;
notSure = 'maybe a string instead';
notSure = false;
当一个函数没有返回值时,通常会见到其返回值类型是void。
function test() void {
console.log('This is function is void');
}
TypeScript里,undefined 和 null 两者各自有自己的类型分别叫做 undefined 和 unll。
let u: undefined = undefined;
let n: null = null;
联合类型(Union Types)表示取值可以为多种类型中的一种。
let myFavoriteNumber: string | number;;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
let num: number = 5;
if (num > 0) {
console.log('数字是正数');
}
let num: number = 12;
if (num % 2 == 0) {
console.log('数字是偶数');
} else {
console.log('数字是奇数');
}
let num: number = 5;
if (num > 0) {
console.log(num + '是正数');
} else if (num < 0) {
console.log(num + '是负数');
} else {
console.log(num + '是0');
}
var grade: string = 'A';
switch(grade) {
case 'A': {
console.log('优');
break;
}
case 'B': {
console.log('良');
break;
}
case 'C': {
console.log('及格');
break;
}
case 'D': {
console.log('不及格');
break;
}
default: {
console.log('非法输入');
break;
}
}
// 有名函数:给变量设置为number类型
function add(x: number, y: number): number {
return x + y;
}
// 匿名函数:给变量设置为number类型
let myAdd = function (x: number, y: number): number {
return x + y;
};
function buildName(firstName: string, lastName?: string) {
if (lastName)
return firstName + ' ' + lastName;
else
return firstName;
}
let result1 = buildName('Bob');
let result2 = buildName('Bob', 'Adams');
剩余参数会被当做个数不限的可选参数。 可以一个都没有,同样也可以有任意个。 可以使用省略号( …)进行定义:
function getEmployeeName(firstName: string, ...restOfName: string[]) {
return firstName + ' ' + restOfName.join(' ');
}
let employeeName = getEmployeeName('Joseph', 'Samuel', 'Lucas', 'MacKinzie');
ES6版本的TypeScript提供了一个箭头函数,它是定义匿名函数的简写语法,用于函数表达式,它省略了function关键字。箭头函数的定义如下,其函数是一个语句块:
( [param1, parma2,…param n] )=> {
// 代码块
}
其中,括号内是函数的入参,可以有0到多个参数,箭头后是函数的代码块。我们可以将这个箭头函数赋值给一个变量,如下所示:
let arrowFun = ( [param1, parma2,…param n] )=> {
// 代码块
}
如果要主动调用这个箭头函数,可以按如下方法去调用:
arrowFun(param1, parma2,…param n)
TypeScript支持基于类的面向对象的编程方式,定义类的关键字为class,后面紧跟类名。类描述了所创
建的对象共同的属性和方法。
声明一个Person类,这个类有3个成员:一个是属性(包含name和age),一个是构造函数,一个是getPersonInfo方法,其定义如下所示。
class Person {
private name: string
private age: number
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
public getPersonInfo(): string {
return `My name is ${this.name} and age is ${this.age}`;
}
}
let person1 = new Person('Jacky', 18);
person1.getPersonInfo();
继承就是子类继承父类的特征和行为,使得子类具有父类相同的行为。TypeScript中允许使用继承来扩展现有的类,对应的关键字为extends。
class Employee extends Person {
private department: string
constructor(name: string, age: number, department: string) {
super(name, age);
this.department = department;
}
public getEmployeeInfo(): string {
return this.getPersonInfo() + ` and work in ${this.department}`;
}
}
let person2 = new Employee('Tom', 28, 'HuaWei');
person2.getPersonInfo();
person2.getEmployeeInfo();
随着应用越来越大,通常要将代码拆分成多个文件,即所谓的模块(module)。模块可以相互加载,并可以使用特殊的指令 export 和 import 来交换功能,从另一个模块调用一个模块的函数。
两个模块之间的关系是通过在文件级别上使用 import 和 export 建立的。模块里面的变量、函数和类等在模块外部是不可见的,除非明确地使用 export 导出它们。类似地,我们必须通过 import 导入其他模块导出的变量、函数、类等。
export class NewsData {
title: string;
content: string;
imagesUrl: Array;
source: string;
constructor(title: string, content: string, imagesUrl: Array, source: string) {
this.title = title;
this.content = content;
this.imagesUrl = imagesUrl;
this.source = source;
}
}
import { NewsData } from '../common/bean/NewsData';
当一个对象实现了Symbol.iterator属性时,我们认为它是可迭代的。一些内置的类型如Array,Map,Set,String,Int32Array,Uint32Array等都具有可迭代性。
let someArray = [1, "string", false];
for (let entry of someArray) {
console.log(entry); // 1, "string", false
}
let list = [4, 5, 6];
for (let i in list) {
console.log(i); // "0", "1", "2",
}
for (let i of list) {
console.log(i); // "4", "5", "6"
}
ArkUI开发框架
基本组成说明如下:
装饰器
用来装饰类、结构体、方法以及变量,赋予其特殊的含义,如上述示例中 @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的主要扩展。
组件状态管理装饰器用来管理组件中的状态,它们分别是:@State、@Prop、@Link。
自定义组件的生命周期函数用于通知用户该自定义组件的生命周期,这些回调函数是私有的,在运行时由开发框架在特定的时间进行调用,不能从应用程序中手动调用这些回调函数。
自定义组件生命周期的简化图示:
应用界面是由一个个页面组成,ArkTS是由ArkUI框架提供,用于以声明式开发范式开发界面的语言。
声明式UI构建页面的过程,其实是组合组件的过程,声明式UI的思想,主要体现在两个方面:
类似苹果的SwiftUI中通过组合视图View,安卓Jetpack Compose中通过组合@Composable函数,ArkUI作为HarmonyOS应用开发的UI开发框架,其使用ArkTS语言构建自定义组件,通过组合自定义组件完成页面的构建。
ArkTS通过struct声明组件名,并通过@Component和@Entry装饰器,来构成一个自定义组件。
使用@Entry和@Component装饰的自定义组件作为页面的入口,会在页面加载时首先进行渲染。
@Entry
@Component
struct ToDoList {...}
使用@Component装饰的自定义组件,如ToDoItem这个自定义组件则对应如下内容,作为页面的组成部分。
@Component
struct ToDoItem {...}
在自定义组件内需要使用build方法来进行UI描述。
@Entry
@Component
struct ToDoList
...
build() {
...
}
}
build方法内可以容纳内置组件和其他自定义组件,如Column和Text都是内置组件,由ArkUI框架提供,ToDoItem为自定义组件,需要开发者使用ArkTS自行声明。
@Entry
@Component
struct ToDoList {
...
build() {
Column(...) {
Text(...)
...
ForEach(...{
TodoItem(...)
},...)
}
...
}
}
自定义组件的组成使用基础组件和容器组件等内置组件进行组合。但有时内置组件的样式并不能满足我们的需求,ArkTS提供了属性方法用于描述界面的样式。
Text('Hello World').fontSize(50)
Text('Hello World').fontSize(this.size)
Text('Hello World')
.fontSize(this.size)
.width(100)
.height(100)
Text('Hello World')
.fontSize(this.size)
.width(this.count + 100)
.height(this.count % 2 === 0 ? 100 : 200)
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布局
Row布局
Row() {
Image($r('app.media.ic_default'))
...
Text(this.content)
...
}
...
类似下图所示的布局,整体都是从上往下纵向排列,适用的布局方式是Column列布局。
Column布局
Column() {
Text($r('app.string.page_title'))
...
ForEach(this.totalTasks,(item) =]]> {
TodoItem({content:item})
},...)
}
实际开发中由于交互,页面的内容可能需要产生变化,以每一个ToDoItem为例,其在完成时的状态与未完成时的展示效果是不一样的。
声明式UI的特点就是UI是随数据更改而自动刷新的,我们这里定义了一个类型为boolean的变量isComplete,其被@State装饰后,框架内建立了数据和视图之间的绑定,其值的改变影响UI的显示。
@State isComplete : boolean = false;
用圆圈和对勾这样两个图片,分别来表示该项是否完成,这部分涉及到内容的切换,需要使用条件渲染if / else语法来进行组件的显示与消失,当判断条件为真时,组件为已完成的状态,反之则为未完成。
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的图片路径。
@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装饰的函数名,即可快速创建布局。
if (this.isComplete) {
this.labelIcon($r('app.media.ic_ok'))
} else {
this.labelIcon($r('app.media.ic_default'))
}
为了让待办项带给用户的体验更符合已完成的效果,给内容的字体也增加了相应的样式变化,这里使用了三目运算符来根据状态变化修改其透明度和文字样式,如opacity是控制透明度,decoration是文字是否有划线。通过isComplete的值来控制其变化。
Text(this.content)
...
.opacity(this.isComplete ? CommonConstants.OPACITY_COMPLETED : CommonConstants.OPACITY_DEFAULT)
.decoration({ type: this.isComplete
? TextDecorationType.LineThrough
: TextDecorationType.None })
最后,为了实现与用户交互的效果,在组件上添加了onClick点击事件,当用户点击该待办项时,数据isComplete的更改就能够触发UI的更新。
@Componentstruct
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;
})
}
}
刚刚只是完成了一个ToDoItem组件的开发,当我们有多条待办数据需要显示在页面时,就需要使用到ForEach循环渲染语法。
例如这里我们有五条待办数据需要展示在页面上。
total_Tasks: Array<string]]> = ['早起晨练','准备早餐','阅读名著','学习ArkTS','看剧放松']
ForEach基本使用中,只需要了解要渲染的数据以及要生成的UI内容两个部分,例如这里要渲染的数组为以上的五条待办事项,要渲染的内容是ToDoItem这个自定义组件,也可以是其他内置组件。
ForEach基本使用
ToDoItem这个自定义组件中,每一个ToDoItem要显示的文本参数content需要外部传入,参数传递使用花括号的形式,用content接受数组内的内容项item。
最终完成的代码及其效果如下。
@Entry
@Componentstruct
ToDoList {
...
build() {
Row() {
Column() {
Text(...)
...
ForEach(this.totalTasks,(item) => {
TodoItem({content:item})
},...)
}
.width('100%')
}
.height('100%')
}
}