鸿蒙ArkTS必知必会语法

546 阅读6分钟

本文基于Api11

语法

静态字段

使用关键字static将字段声明为静态。静态字段属于类本身,类的所有实例共享一个静态字段。

要访问静态字段,需要使用类名:

class Person {
  static numberOfPersons = 0
  constructor() {
     // ...
     Person.numberOfPersons++;
     // ...
  }
}

Person.numberOfPersons;

字段初始化

为了减少运行时的错误和获得更好的执行性能,ArkTS要求所有字段在声明时或者构造函数中显式初始化。这和标准TS中的strictPropertyInitialization模式一样。

class Person {
  name: string = ''
  
  setName(n:string): void {
    this.name = n;
  }
  
  // 类型为'string',不可能为"null"或者"undefined"
  getName(): string {
    return this.name;
  }
}

如果不赋初始值,要声明可空?

class Person {
  name?: string

  setName(n:string): void {
    this.name = n;
  }

  getName(): string | null | undefined{
    return this.name;
  }
}

箭头函数

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

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

箭头右边可以是函数体,也可以是单个表达式:

let sum1 = (x: number, y: number) => { return x + y; }
let sum2 = (x: number, y: number) => x + y

说一个特殊的,ArkTS中不允许用对象字面量来声明类型,比如在系统源码中看到如下代码,在实际代码编写时是不允许的:

//这个箭头函数的返回值是对象字面量,也就系统源码历史遗留这么写,业务代码中这么写IDE会报错
onScrollFrameBegin(event: (offset: number, state: ScrollState) => {
    offsetRemain: number;
}): ScrollAttribute;

可选链(带?调用属性)

在访问对象属性时,如果该属性是undefined或者null,可选链运算符会返回undefined

class Person {
  nick: string | null = null
  spouse?: Person

  constructor(nick: string) {
    this.nick = nick;
    this.spouse = undefined;
  }
  setSpouse(spouse: Person): void {
    this.spouse = spouse;
  }

  // 返回类型必须为string | null | undefined,因为该方法可能返回null或者undefined
  getSpouseNick(): string | null | undefined {
    return this.spouse?.nick;
  }
}

导出

可以使用关键字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));
}

符号和关键字

|(联合类型)理解为‘单选’

  • 描述:一个值可以是几种类型中的一种。
  • 用途:用于定义一个变量可以是多个不同类型中的一种。
  • 例子:
    type StringOrNumber = string | number;
    let value: StringOrNumber;
    value = "hello"; // 合法
    value = 123;     // 合法
    value = true;    // 不合法
    

&(合并类型)理解为‘全选’

  • 描述:一个值必须同时满足几个类型的要求。
  • 用途:用于合并多个类型,生成一个具有所有属性的新类型。
  • 例子:
    type A = { name: string; }; //系统源码中有这样声明的,但ArkTS不支持使用对象字面量声明类型
    type B = { age: number; }; //系统源码中有这样声明的,但ArkTS不支持使用对象字面量声明类型
    type C = A & B; //可以理解为C继承了A和B
    let person: C = { name: "John", age: 30 }; 
    
    注意:虽然系统源码中有上面这么写的,但目前ArkTS不支持代码中这么写,可以使用继承作为替代方案,比如:
    interface Identity {
      id: number
      name: string
    }
    
    interface Contact {
      email: string
      phoneNumber: string
    }
    
    interface Employee extends Identity,  Contact {}
    

Partial(混合类型多选)理解为‘多选’

  • 描述:将某个类型的所有属性变为可选属性。
  • 用途:用于创建一个新类型,使得原本必需的属性变为可选。
  • 例子:
    type MethodsHeaders = Partial<{[Key in Method as Lowercase<Key>]: AxiosHeaders;} & {common: AxiosHeaders}>;
    
    所有这些属性都可以同时存在于一个对象中;Partial 使所有属性变为可选。

对象字面量

对象字面量是一个表达式,可用于创建类的实例并提供一些初始值。它在某些情况下更方便,可以用来代替new表达式

对象字面量的表示方式是:封闭在花括号{ } 中的属性名:值的列表。

class C {
  n: number = 0
  s: string = ''
}

let c: C = {n: 42, s: 'foo'};

ArkTS是静态类型语言,如上述示例所示,对象字面量只能在可以推导出该字面量类型的上下文中使用。其他正确的例子:

class C {
  n: number = 0
  s: string = ''
}

let c: C
c = {n: 42, s: 'foo'};  // 使用变量的类型

function foo(c: C) {}
foo({n: 42, s: 'foo'}); // 使用参数的类型

function bar(): C {
  return {n: 42, s: 'foo'}; // 使用返回类型
}

Record类型的对象字面量

泛型Record<K, V>用于将类型(键类型)的属性映射到另一个类型(值类型)。常用对象字面量来初始化该类型的值:

let map: Record<string, number> = {
  'John': 25,
  'Mary': 21,
}

map['John']; // 25

类型K可以是字符串类型或数值类型,而V可以是任何类型。

interface PersonInfo {
  age: number
  salary: number
}
let map: Record<string, PersonInfo> = {
  'John': { age: 25, salary: 10},
  'Mary': { age: 21, salary: 20}
}

as 类型强转

export let contactsGroups: object[] = [
  {
    title: 'A',
    contacts: [
      new Contact('艾佳', $r('app.media.iconA')),
      new Contact('安安', $r('app.media.iconB')),
      new Contact('Angela', $r('app.media.iconC')),
    ],
    key: util.generateRandomUUID(true)
  } as ContactsGroup,
  {
    title: 'B',
    contacts: [
      new Contact('白叶', $r('app.media.iconD')),
      new Contact('伯明', $r('app.media.iconE')),
    ],
    key: util.generateRandomUUID(true)
  } as ContactsGroup,
  // ...
]

Aliases类型

Aliases类型为匿名类型(数组、函数、对象字面量或联合类型)提供名称,或为已有类型提供替代名称

type Matrix = number[][];
type Handler = (s: string, no: number) => string;
type Predicate <T> = (x: T) => Boolean;
type NullableObject = Object | null;

泛型默认值

class SomeType {}

//泛型设置了默认值
interface Interface <T1 = SomeType> { }
class Base <T2 = SomeType> { }
//下面这俩写法一样
class Derived1 extends Base implements Interface { }
class Derived2 extends Base<SomeType> implements Interface<SomeType> { }

//泛型设置了默认值
function foo<T = number>(): T { }
//下面这俩写法一样
foo();
foo<number>();

空安全

默认情况下,ArkTS中的所有类型都是不可为空的,因此类型的值不能为空。

在下面的示例中,所有行都会导致编译时错误:

let x: number = null;    // 编译时错误
let y: string = null;    // 编译时错误
let z: number[] = null;  // 编译时错误

可以为空值的变量定义为联合类型T | null。

let x: number | null = null;
x = 1;    // ok
x = null; // ok
if (x != null) { }

非空断言运算符

后缀运算符!可用于断言其操作数为非空。

应用于空值时,运算符将抛出错误。

class C {
  value: number | null = 1;
}

let y: number;
let c = new C();

y = c.value + 1;  // 编译时错误:无法对可空值作做加法
y = c.value! + 1; // ok,值为2

空值合并运算符

a ?? b等价于三元运算符(a != null && a != undefined) ? a : b

class Person {
  // ...
  nick: string | null = null
  getNick(): string {
    return this.nick ?? '';
  }
}

接口只有一个无名函数,可以直接当函数用

注意,系统源码中虽然有这样定义的call signature,但是在自己代码中不允许这么定义,编译器会报错 arkts-no-call-signatures

export interface AsyncCallback<T, E = void> {
    (err: BusinessError<E>, data: T): void; //call signature
}
loadContent(path: string, callback: AsyncCallback<void>): void;

windowStage.loadContent(RouterUrl.SPLASH, (err, data) => {
    // 函数体
});

使用API

Object.keys

  • 返回对象的所有一级属性。
  • 不会返回对象从其原型链上继承的属性,只返回对象自身直接定义的属性。
  • 不返回嵌套对象的属性: 对于嵌套的对象属性,Object.keys只会返回最外层的属性名,不会递归到嵌套对象内部。
let obj : User = { a: 1, b: 2, c: 3 };
let keys = Object.keys(obj);
console.log(keys); // 输出: ['a', 'b', 'c']

需要注意的是,Object.keys只返回对象自身的可枚举属性,不包括继承的属性。