11-从TypeScript到ArkTS的适配规则(1)

2024-12-12 21:16:20
128次阅读
0个评论

11-从TypeScript到ArkTS的适配规则(1)

对于学习过Typescript的同学,要迁移到ArkTs上的时候,需要注意部分语法是不支持的。主要的特点是取消了动态属性等能力。

强制使用静态类型

静态类型是ArkTS最重要的特性之一。如果程序采用静态类型,即所有类型在编译时都是已知的,那么开发者就能够容易理解代码中使用了哪些数据结构。同时,由于所有类型在程序实际运行前都是已知的,编译器可以提前验证代码的正确性,从而可以减少运行时的类型检查,有助于提升性能。

基于上述考虑,ArkTS中禁止使用any类型。

示例

// 不支持:let res: any = some_api_function('hello', 'world');// `res`是什么?错误代码的数字?字符串?对象?// 该如何处理它?// 支持:class CallResult {  public succeeded(): boolean { ... }  public errorMessage(): string { ... }}
let res: CallResult = some_api_function('hello', 'world');if (!res.succeeded()) {  console.log('Call failed: ' + res.errorMessage());}

any类型在TypeScript中并不常见,只有大约1%的TypeScript代码库使用。一些代码检查工具(例如ESLint)也制定一系列规则来禁止使用any。因此,虽然禁止any将导致代码重构,但重构量很小,有助于整体性能提升。

禁止在运行时变更对象布局

为实现最佳性能,ArkTS要求在程序执行期间不能更改对象的布局。换句话说,ArkTS禁止以下行为:

  • 向对象中添加新的属性或方法。
  • 从对象中删除已有的属性或方法。
  • 将任意类型的值赋值给对象属性。

TypeScript编译器已经禁止了许多此类操作。然而,有些操作还是有可能绕过编译器的,例如,使用as any转换对象的类型,或者在编译TS代码时关闭严格类型检查的配置,或者在代码中通过@ts-ignore忽略类型检查。

在ArkTS中,严格类型检查不是可配置项。ArkTS强制进行部分严格类型检查,并通过规范禁止使用any类型,禁止在代码中使用@ts-ignore。

示例

class Point {  public x: number = 0  public y: number = 0
  constructor(x: number, y: number) {    this.x = x;    this.y = y;  }}
// 无法从对象中删除某个属性,从而确保所有Point对象都具有属性xlet p1 = new Point(1.0, 1.0);delete p1.x;           // 在TypeScript和ArkTS中,都会产生编译时错误delete (p1 as any).x;  // 在TypeScript中不会报错;在ArkTS中会产生编译时错误
// Point类没有定义命名为z的属性,在程序运行时也无法添加该属性let p2 = new Point(2.0, 2.0);p2.z = 'Label';           // 在TypeScript和ArkTS中,都会产生编译时错误(p2 as any).z = 'Label';   // 在TypeScript中不会报错;在ArkTS中会产生编译时错误
// 类的定义确保了所有Point对象只有属性x和y,并且无法被添加其他属性let p3 = new Point(3.0, 3.0);let prop = Symbol();      // 在TypeScript中不会报错;在ArkTS中会产生编译时错误(p3 as any)[prop] = p3.x; // 在TypeScript中不会报错;在ArkTS中会产生编译时错误p3[prop] = p3.x;          // 在TypeScript和ArkTS中,都会产生编译时错误
// 类的定义确保了所有Point对象的属性x和y都具有number类型,因此,无法将其他类型的值赋值给它们let p4 = new Point(4.0, 4.0);p4.x = 'Hello!';          // 在TypeScript和ArkTS中,都会产生编译时错误(p4 as any).x = 'Hello!'; // 在TypeScript中不会报错;在ArkTS中会产生编译时错误
// 使用符合类定义的Point对象: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)  );}let p5 = new Point(5.0, 5.0);let p6 = new Point(6.0, 6.0);console.log('Distance between p5 and p6: ' + distance(p5, p6));

修改对象布局会影响代码的可读性以及运行时性能。从开发者的角度来说,在某处定义类,然后又在其他地方修改实际的对象布局,很容易引起困惑乃至引入错误。此外,这点还需要额外的运行时支持,增加了执行开销。这一点与静态类型的约束也冲突:既然已决定使用显式类型,为什么还需要添加或删除属性呢?

当前,只有少数项目允许在运行时变更对象布局,一些常用的代码检查工具也增加了相应的限制规则。这个约束只会导致少量代码重构,但会提升性能。

限制运算符的语义

为获得更好的性能并鼓励开发者编写更清晰的代码,ArkTS限制了一些运算符的语义。详细的语义限制,请参考约束说明

示例

// 一元运算符`+`只能作用于数值类型:let t = +42;   // 合法运算let s = +'42'; // 编译时错误

使用额外的语义重载语言运算符会增加语言规范的复杂度,而且,开发者还被迫牢记所有可能的例外情况及对应的处理规则。在某些情况下,产生一些不必要的运行时开销。

当前只有不到1%的代码库使用该特性。因此,尽管限制运算符的语义需要重构代码,但重构量很小且非常容易操作,并且,通过重构能使代码更清晰、具备更高性能。

不支持 structural typing

假设两个不相关的类T和U拥有相同的publicAPI:

class T {  public name: string = ''
  public greet(): void {    console.log('Hello, ' + this.name);  }}
class U {  public name: string = ''
  public greet(): void {    console.log('Greetings, ' + this.name);  }}

能把类型为T的值赋给类型为U的变量吗?

let u: U = new T(); // 是否允许?

能把类型为T的值传递给接受类型为U的参数的函数吗?

function greeter(u: U) {  console.log('To ' + u.name);  u.greet();}
let t: T = new T();greeter(t); // 是否允许?

换句话说,我们将采取下面哪种方法呢:

  • T和U没有继承关系或没有implements相同的接口,但由于它们具有相同的publicAPI,它们“在某种程度上是相等的”,所以上述两个问题的答案都是“是”;
  • T和U没有继承关系或没有implements相同的接口,应当始终被视为完全不同的类型,因此上述两个问题的答案都是“否”。

采用第一种方法的语言支持structural typing,而采用第二种方法的语言则不支持structural typing。目前TypeScript支持structural typing,而ArkTS不支持。

structural typing是否有助于生成清晰、易理解的代码,关于这一点并没有定论。那为什么ArkTS不支持structural typing呢?

因为对structural typing的支持是一个重大的特性,需要在语言规范、编译器和运行时进行大量的考虑和仔细的实现。另外,由于ArkTS使用静态类型,运行时为了支持这个特性需要额外的性能开销。

鉴于此,当前我们还不支持该特性。根据实际场景的需求和反馈,我们后续会重新加以考虑。更多案例和建议请参考约束说明

约束说明

对象的属性名必须是合法的标识符

**规则:**arkts-identifiers-as-prop-names

级别:错误

在ArkTS中,对象的属性名不能为数字或字符串。例外:ArkTS支持属性名为字符串字面量和枚举中的字符串值。通过属性名访问类的属性,通过数值索引访问数组元素。

TypeScript

var x = { 'name': 'x', 2: '3' };
console.log(x['name']);console.log(x[2]);

ArkTS

class X {  public name: string = ''}let x: X = { name: 'x' };console.log(x.name);
let y = ['a', 'b', 'c'];console.log(y[2]);
// 在需要通过非标识符(即不同类型的key)获取数据的场景中,使用Map<Object, some_type>。let z = new Map<Object, string>();z.set('name', '1');z.set(2, '2');console.log(z.get('name'));console.log(z.get(2));
enum Test {  A = 'aaa',  B = 'bbb'}
let obj: Record<string, number> = {  [Test.A]: 1,   // 枚举中的字符串值  [Test.B]: 2,   // 枚举中的字符串值  ['value']: 3   // 字符串字面量}

不支持Symbol()API

**规则:**arkts-no-symbol

级别:错误

TypeScript中的Symbol()API用于在运行时生成唯一的属性名称。由于该API的常见使用场景在静态类型语言中没有意义,因此,ArkTS不支持Symbol()API。在ArkTS中,对象布局在编译时就确定了,且不能在运行时被更改。

ArkTS只支持Symbol.iterator。

不支持以#开头的私有字段

**规则:**arkts-no-private-identifiers

级别:错误

ArkTS不支持使用#符号开头声明的私有字段。改用private关键字。

TypeScript

class C {  #foo: number = 42}

ArkTS

class C {  private foo: number = 42}

类型、命名空间的命名必须唯一

**规则:**arkts-unique-names

级别:错误

类型(类、接口、枚举)、命名空间的命名必须唯一,且与其他名称(例如:变量名、函数名)不同。

TypeScript

let X: stringtype X = number[] // 类型的别名与变量同名

ArkTS

let X: stringtype T = number[] // 为避免名称冲突,此处不允许使用X

使用let而非var

**规则:**arkts-no-var

级别:错误

let关键字可以在块级作用域中声明变量,帮助程序员避免错误。因此,ArkTS不支持var,请使用let声明变量。

TypeScript

function f(shouldInitialize: boolean) {  if (shouldInitialize) {     var x = 'b';  }  return x;}
console.log(f(true));  // bconsole.log(f(false)); // undefined
let upperLet = 0;{  var scopedVar = 0;  let scopedLet = 0;  upperLet = 5;}scopedVar = 5; // 可见scopedLet = 5; // 编译时错误

ArkTS

function f(shouldInitialize: boolean): string {  let x: string = 'a';  if (shouldInitialize) {    x = 'b';  }  return x;}
console.log(f(true));  // bconsole.log(f(false)); // a
let upperLet = 0;let scopedVar = 0;{  let scopedLet = 0;  upperLet = 5;}scopedVar = 5;scopedLet = 5; //编译时错误

使用具体的类型而非any或unknown

**规则:**arkts-no-any-unknown

级别:错误

ArkTS不支持any和unknown类型。显式指定具体类型。

TypeScript

let value1: anyvalue1 = true;value1 = 42;
let value2: unknownvalue2 = true;value2 = 42;

ArkTS

let value_b: boolean = true; // 或者 let value_b = truelet value_n: number = 42; // 或者 let value_n = 42let value_o1: Object = true;let value_o2: Object = 42;

使用class而非具有call signature的类型

**规则:**arkts-no-call-signatures

级别:错误

ArkTS不支持对象类型中包含call signature。

TypeScript

type DescribableFunction = {  description: string  (someArg: string): string // call signature}
function doSomething(fn: DescribableFunction): void {  console.log(fn.description + ' returned ' + fn(''));}

ArkTS

class DescribableFunction {  description: string  public invoke(someArg: string): string {    return someArg;  }  constructor() {    this.description = 'desc';  }}
function doSomething(fn: DescribableFunction): void {  console.log(fn.description + ' returned ' + fn.invoke(''));}
doSomething(new DescribableFunction());

使用class而非具有构造签名的类型

**规则:**arkts-no-ctor-signatures-type

级别:错误

ArkTS不支持对象类型中的构造签名。改用类。

TypeScript

class SomeObject {}
type SomeConstructor = {  new (s: string): SomeObject}
function fn(ctor: SomeConstructor) {  return new ctor('hello');}

ArkTS

class SomeObject {  public f: string  constructor (s: string) {    this.f = s;  }}
function fn(s: string): SomeObject {  return new SomeObject(s);}

仅支持一个静态块

**规则:**arkts-no-multiple-static-blocks

级别:错误

ArkTS不允许类中有多个静态块,如果存在多个静态块语句,请合并到一个静态块中。

TypeScript

class C {  static s: string
  static {    C.s = 'aa'  }  static {    C.s = C.s + 'bb'  }}

ArkTS

class C {  static s: string
  static {    C.s = 'aa'    C.s = C.s + 'bb'  }}

说明

当前不支持静态块的语法。支持该语法后,在.ets文件中使用静态块须遵循本约束。

不支持index signature

**规则:**arkts-no-indexed-signatures

级别:错误

ArkTS不允许index signature,改用数组。

TypeScript

// 带index signature的接口:interface StringArray {  [index: number]: string}
function getStringArray(): StringArray {  return ['a', 'b', 'c'];}
const myArray: StringArray = getStringArray();const secondItem = myArray[1];

ArkTS

class X {  public f: string[] = []}
let myArray: X = new X();const secondItem = myArray.f[1];

使用继承而非intersection type

**规则:**arkts-no-intersection-types

级别:错误

目前ArkTS不支持intersection type,可以使用继承作为替代方案。

TypeScript

interface Identity {  id: number  name: string}
interface Contact {  email: string  phoneNumber: string}
type Employee = Identity & Contact

ArkTS

interface Identity {  id: number  name: string}
interface Contact {  email: string  phoneNumber: string}
interface Employee extends Identity,  Contact {}
收藏00

登录 后评论。没有帐号? 注册 一个。