写点什么

鸿蒙 NEXT 开发 -ArkTS

作者:东林知识库
  • 2025-03-31
    江苏
  • 本文字数:6523 字

    阅读完需:约 21 分钟

1. ArkTS 基本介绍

ArkTS 是 HarmonyOS 优选的主力应用开发语言。


ArkTS 围绕应用开发在 TypeScript(简称 TS)生态基础上做了进一步扩展,保持了 TS 的基本风格,同时通过规范定义强化开发期静态检查和分析,提升程序执行稳定性和性能。


从 API version 10 开始,ArkTS 进一步通过规范强化静态检查和分析,对比标准 TS 的差异可以参考从 TypeScript 到 ArkTS 的适配规则:


  • 强制使用静态类型:静态类型是 ArkTS 最重要的特性之一。如果使用静态类型,那么程序中变量的类型就是确定的。同时,由于所有类型在程序实际运行前都是已知的,编译器可以验证代码的正确性,从而减少运行时的类型检查,有助于性能提升。

  • 禁止在运行时改变对象布局:为实现最大性能,ArkTS 要求在程序执行期间不能更改对象布局。

  • 限制运算符语义:为获得更好的性能并鼓励开发者编写更清晰的代码,ArkTS 限制了一些运算符的语义。比如,一元加法运算符只能作用于数字,不能用于其他类型的变量。

  • 不支持 Structural typing:对 Structural typing 的支持需要在语言、编译器和运行时进行大量的考虑和仔细的实现,当前 ArkTS 不支持该特性。根据实际场景的需求和反馈,我们后续会重新考虑。


ArkTS 它是纯新的一门语言,它不是前端也不是 TypeScript,它是 TS 的超集



  • ArkTS 的特性-扩展能力


  1. 基本语法


  • 定义声明式 UI、自定义组件、动态扩展 UI 元素;

  • 提供 ArkUI 系统组件,提供组件事件、方法、属性;

  • 共同构成 UI 开发主体


  1. 状态管理


  • 组件状态、组件数据共享、应用数据共享、设备共享;


  1. 渲染控制


  • 条件渲染、循环渲染、数据懒加载;


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


  • 命令式 UI- document.createElement("div")- <div>

  • 声明式 UI


ArkTS 提供了标准内置对象,例如 Array、Map、TypedArray、Math 等,供开发者直接使用。另外,ArkTS 也提供了语言基础类库,为应用开发者提供常用的基础能力,主要包含能力如下图所示。


2. ArkTS 语法

ArkTS 通过声明引入变量、常量、函数和类型。

2.1 变量声明

以关键字 let 开头的声明引入变量,该变量在程序执行期间可以具有不同的值。


let hi: string = 'hello';hi = 'hello, world';
复制代码

2.2 常量声明

以关键字 const 开头的声明引入只读常量,该常量只能被赋值一次。


const hello: string = 'hello';
复制代码


对常量重新赋值会造成编译时错误。


变量、常量的命名规则:


  • 只能包含数字、字母、下划线、$,不能以数字开头

  • 不能使用内置关键字或保留字(比如 let const)

  • 严格区分大小写

2.3 自动类型推断

由于 ArkTS 是一种静态类型语言,所有数据的类型都必须在编译时确定。


但是,如果一个变量或常量的声明包含了初始值,那么开发者就不需要显式指定其类型。ArkTS 规范中列举了所有允许自动推断类型的场景。


以下示例中,两条声明语句都是有效的,两个变量都是 string 类型:


let hi1: string = 'hello';let hi2 = 'hello, world';
复制代码

2.4 联合类型

联合类型是一种灵活的数据类型,它修饰的变量可以存储不同类型的数据;


语法:


let 变量:类型 1|类型 2|类型 3 =值


let name:string|number;name=1name='东林'
复制代码

2.5 枚举类型

枚举类型是一种特殊的数据类型,约定变量只能在一组数据范围内选择值


语法:


enum 枚举名{


常量 1=值,


常量 2=值


}


enum Color{  Red='#ff0f29',  Green='#30b30e'}// 使用枚举let color:Color=Color.Red
复制代码

2.6 数组

ArkTS 基本数据类型(string、number、boolean 等,语法跟 ts 差不多,大家可以在东林录制的视频前置课里面学习)


数组,是一个容器,可以存储多个数据;


语法:


let 数组名:类型[]=[数据1,数据2,....]


let names:string[]=['小头','东林']
复制代码


注意:数组指定的类型和存储的数据类型必须要一致,否则会报错


数组中存储的每个数据,都有自己的编号,编号从 0 开始(索引)


联合类型:


let names:(string|number)[]=[1,2,'小头']
复制代码

2.7 函数

函数:是可以被重复使用的代码块


定义函数:


function 函数名(){ // ...}
复制代码


调用函数:


函数名()


函数的完整用法


function add(x: string, y: string): string {  let z: string = `${x} ${y}`;  return z;}
复制代码

2.7.1 可选参数

可选参数的格式可为 name?: Type


function hello(name?: string) {  if (name == undefined) {    console.log('Hello!');  } else {    console.log(`Hello, ${name}!`);  }}
复制代码


可选参数的另一种形式为设置的参数默认值。如果在函数调用中这个参数被省略了,则会使用此参数的默认值作为实参。


function multiply(n: number, coeff: number = 2): number {  return n * coeff;}multiply(2);  // 返回2*2multiply(2, 3); // 返回2*3
复制代码

2.7.2 Rest 参数

函数的最后一个参数可以是 rest 参数。使用 rest 参数时,允许函数或方法接受任意数量的实参。


function sum(...numbers: number[]): number {  let res = 0;  for (let n of numbers)    res += n;  return res;}
sum() // 返回0sum(1, 2, 3) // 返回6
复制代码

2.7.3 返回类型

如果可以从函数体内推断出函数返回类型,则可在函数声明中省略标注返回类型。


// 显式指定返回类型function foo(): string { return 'foo'; }
// 推断返回类型为stringfunction goo() { return 'goo'; }
复制代码


不需要返回值的函数的返回类型可以显式指定为 void 或省略标注。这类函数不需要返回语句。

2.7.4 箭头函数(又名 Lambda 函数)

箭头函数是比普通函数更简洁的一种函数写法


函数可以定义为箭头函数,例如:


let sum = (x: number, y: number): number => {  return x + y;}
复制代码

2.8 类

类声明引入一个新类型,并定义其字段、方法和构造函数。


在以下示例中,定义了 Person 类,该类具有字段 name 和 surname、构造函数和方法 fullName:


class Person {  name: string = ''  surname: string = ''  constructor (n: string, sn: string) {    this.name = n;    this.surname = sn;  }  fullName(): string {    return this.name + ' ' + this.surname;  }}
复制代码


定义类后,可以使用关键字 new 创建实例:


let p = new Person('John', 'Smith');console.log(p.fullName());
复制代码

2.9 接口

接口声明引入新类型。接口是定义代码协定的常见方式。


任何一个类的实例只要实现了特定接口,就可以通过该接口实现多态。


接口通常包含属性和方法的声明


interface Style {  color: string // 属性}interface AreaSize {  calculateAreaSize(): number // 方法的声明  someMethod(): void;     // 方法的声明}
复制代码


实现接口的类示例:


// 接口:interface AreaSize {  calculateAreaSize(): number // 方法的声明  someMethod(): void;     // 方法的声明}
// 实现:class RectangleSize implements AreaSize { private width: number = 0 private height: number = 0 someMethod(): void { console.log('someMethod called'); } calculateAreaSize(): number { this.someMethod(); // 调用另一个方法并返回结果 return this.width * this.height; }}
复制代码

2.10 对象

语法:let 对象名称:对应结构类型=值

2.10.1 接口类型

interface People{  name:string  age:number}
let pick:People={ name:'pick', age:1}
复制代码

2.10.2 类类型

class People{  name:string=''  age:number=0}
let pick:People={ name:'pick', age:1}
复制代码

2.11 泛型类型和函数

泛型类型和函数允许创建的代码在各种类型上运行,而不仅支持单一类型。

2.11.1 泛型类和接口

类和接口可以定义为泛型,将参数添加到类型定义中,如以下示例中的类型参数 Element:


class CustomStack<Element> {  public push(e: Element):void {    // ...  }}
复制代码


要使用类型 CustomStack,必须为每个类型参数指定类型实参:


let s = new CustomStack<string>();s.push('hello');
复制代码

2.11.2 泛型约束

泛型类型的类型参数可以被限制只能取某些特定的值。例如,MyHashMap<Key, Value>这个类中的 Key 类型参数必须具有 hash 方法。


interface Hashable {  hash(): number}class MyHashMap<Key extends Hashable, Value> {  public set(k: Key, v: Value) {    let h = k.hash();    // ...其他代码...  }}
复制代码


在上面的例子中,Key 类型扩展了 Hashable,Hashable 接口的所有方法都可以为 key 调用。

2.11.3 泛型函数

使用泛型函数可编写更通用的代码。比如返回数组最后一个元素的函数:


function last(x: number[]): number {  return x[x.length - 1];}last([1, 2, 3]); // 3
复制代码


如果需要为任何数组定义相同的函数,使用类型参数将该函数定义为泛型:


function last<T>(x: T[]): T {  return x[x.length - 1];}
复制代码

2.12 语句

2.12.1 if 语句

if 语句用于需要根据逻辑条件执行不同语句的场景。当逻辑条件为真时,执行对应的一组语句,否则执行另一组语句(如果有的话)。


else 部分也可能包含 if 语句。


if 语句如下所示:


if (condition1) {  // 语句1} else if (condition2) {  // 语句2} else {  // else语句}
复制代码


条件表达式可以是任何类型。但是对于 boolean 以外的类型,会进行隐式类型转换:


let s1 = 'Hello';if (s1) {  console.log(s1); // 打印“Hello”}
let s2 = 'World';if (s2.length != 0) { console.log(s2); // 打印“World”}
复制代码

2.12.2 Switch 语句

使用 switch 语句来执行与 switch 表达式值匹配的代码块。


switch 语句如下所示:


switch (expression) {  case label1: // 如果label1匹配,则执行    // ...    // 语句1    // ...    break; // 可省略  case label2:  case label3: // 如果label2或label3匹配,则执行    // ...    // 语句23    // ...    break; // 可省略  default:    // 默认语句}
复制代码

2.12.3 条件表达式

条件表达式由第一个表达式的布尔值来决定返回其它两个表达式中的哪一个。


      condition ? expression1 : expression2
复制代码


如果 condition 的为真值(转换后为 true 的值),则使用 expression1 作为该表达式的结果;否则,使用 expression2。

2.12.4 For 语句

for 语句会被重复执行,直到循环退出语句值为 false。


for ([init]; [condition]; [update]) {  statements}
复制代码

2.12.5 For-of 语句

使用 for-of 语句可遍历数组或字符串。示例如下:


for (forVar of expression) {  statements}
复制代码


for (let ch of 'a string object') {  /* process ch */}
复制代码

2.12.6 While 语句

只要 condition 为真值(转换后为 true 的值),while 语句就会执行 statements 语句。示例如下:


while (condition) {  statements}
复制代码


let n = 0;let x = 0;while (n < 3) {  n++;  x += n;}
复制代码

2.12.7 Do-while 语句

如果 condition 的值为真值(转换后为 true 的值),那么 statements 语句会重复执行。示例如下:


do {  statements} while (condition)
复制代码


let i = 0;do {  i += 1;} while (i < 10)
复制代码

2.12.8 Break 语句

使用 break 语句可以终止循环语句或 switch。


let x = 0;while (true) {  x++;  if (x > 5) {    break;  }}
复制代码

2.12.9 Continue 语句

continue 语句会停止当前循环迭代的执行,并将控制传递给下一个迭代。


let sum = 0;for (let x = 0; x < 100; x++) {  if (x % 2 == 0) {    continue  }  sum += x;}
复制代码

2.13 模块

程序可划分为多组编译单元或模块。


每个模块都有其自己的作用域,即,在模块中创建的任何声明(变量、函数、类等)在该模块之外都不可见,除非它们被显式导出。


与此相对,从另一个模块导出的变量、函数、类、接口等必须首先导入到模块中。

2.13.1 导出

可以使用关键字 export 导出顶层的声明。


未导出的声明名称被视为私有名称,只能在声明该名称的模块中使用。


注意:通过 export 方式导出,在导入时要加{}。


export class Point {  x: number = 0  y: number = 0  constructor(x: number, y: number) {    this.x = x;    this.y = y;  }}export let Origin = new Point(0, 0);export function Distance(p1: Point, p2: Point): number {  return Math.sqrt((p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y));}
复制代码

2.13.2 导入

导入声明用于导入从其他模块导出的实体,并在当前模块中提供其绑定。导入声明由两部分组成:


  • 导入路径,用于指定导入的模块;

  • 导入绑定,用于定义导入的模块中的可用实体集和使用形式(限定或不限定使用)。


导入绑定可以有几种形式。


假设模块具有路径“./utils”和导出实体“X”和“Y”。


导入绑定* as A 表示绑定名称“A”,通过 A.name 可访问从导入路径指定的模块导出的所有实体:


import * as Utils from './utils'Utils.X // 表示来自Utils的XUtils.Y // 表示来自Utils的Y
复制代码


导入绑定{ ident1, ..., identN }表示将导出的实体与指定名称绑定,该名称可以用作简单名称:


import { X, Y } from './utils'X // 表示来自utils的XY // 表示来自utils的Y
复制代码


如果标识符列表定义了 ident as alias,则实体 ident 将绑定在名称 alias 下:


import { X as Z, Y } from './utils'Z // 表示来自Utils的XY // 表示来自Utils的YX // 编译时错误:'X'不可见
复制代码

3. ArkTS 组件结构

ArkTS 的基本组成



说明


自定义变量不能与基础通用属性/事件名重复。


ArkTS 通过装饰器 @Component@Entry 装饰 struct 关键字声明的数据结构,构成一个自定义组件。自定义组件中提供了一个 build 函数,开发者需在该函数内以链式调用的方式进行基本的 UI 描述,UI 描述的方法请参考 UI 描述规范。

3.1 基于 struct 实现自定义组件

要想实现一段 UI 的描述,必须使用 struct 关键字来声明- 注意不能有继承关系-组件名不能和系统组件名重名


语法: struct 组件名 {}


@Component  struct Index {
}
复制代码


struct 关键字声明的 UI 描述-必须被 @Component 修饰

3.2 Component 修饰符

Component 装饰器只能修饰 struct 关键字声明的结构,被修饰后的 struct 具备组件的描述(渲染)能力

3.3 Build 函数

用于定义组件的 UI 描述,一个 struct 结构必须实现 build 函数


@Component  struct MyComponent {    build() {    }  }
复制代码

3.4 entry 修饰符

entry 将自定义组件定义为 UI 页面的入口,也就是我们原来前端常说的一个页面,最多可以使用 entry 装饰一个自定义组件(在一个 ets 文件中)-如下面的代码就是不被允许的


@Entry  @Component  struct Index {
build() {
} }@Entry @Component struct Index2 { build() {
} }
复制代码


entry 修饰的组件,最终会被注册,具体文件位置-main/resources/base/profile/main_pages.json


  1. 自动注册-新建组件时,采用新建 Page 的方式

  2. 手动注册-新建一个 ets 文件,自己在 main_pages.json 中手动添加路径

3.5 组件复用

在很多情况下,由于业务的复杂度,我们经常会将一个大的业务拆成若干个组件,进行组装,这里我们非常灵活的复用组件,比如


[图片上传失败...(image-f88c6a-1743388250388)]


编辑


  • 我们可以把上图抽象成三个组件- Header- Main- Footer


import Header from '../pages/Header';import Main from '../pages/Main';import Foot from '../pages/Foot';
@Entry@Componentstruct Demo { build() { Column() { Header().backgroundColor(Color.Pink) Main().backgroundColor(Color.Green) Foot().backgroundColor(Color.Red) }.height('100%') .width('100%') }}
复制代码


@Component  export default struct Header {    build() {      Row() {        Text('头部')      }      .width('100%')        .height(60).justifyContent(FlexAlign.Center)    }  }
复制代码


@Component  export default struct Main{    build() {      Row(){        Text('主体')      }      .width('100%').height('80%')        .justifyContent(FlexAlign.Center)    }  }
复制代码


@Component  export  default  struct Foot{    build() {      Row(){        Text('底部')      }      .width('100%').height('10%')        .justifyContent(FlexAlign.Center)    }  }
复制代码


总结:


  • 一个 UI 描述必须使用 struct 来声明,不能继承

  • struct 必须被 Component 或者 CustomDialog 修饰

  • struct 必须实现 build 方法,build 方法可以没有元素,但是有的话有且只有一个可容纳子组件的容器组件(entry 修饰的组件)

  • entry 修饰符表示该组件是页面级组件,一个文件中只允许修饰一个 struct 组件

  • 采用分拆组件的形式可以有效解解耦我们的业务

发布于: 刚刚阅读数: 6
用户头像

享受当下,享受生活,享受成长乐趣! 2025-02-26 加入

鸿蒙、Java、大数据

评论

发布
暂无评论
鸿蒙NEXT开发-ArkTS_东林知识库_InfoQ写作社区