11-从TypeScript到ArkTS的适配规则(1)
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 {}
- 0回答
- 3粉丝
- 0关注
- 12-从TypeScript到ArkTS的适配规则(2)
- 13-从TypeScript到ArkTS的适配规则(3)
- 14-从TypeScript到ArkTS的适配规则(4)
- 15-从 TypeScript 到 ArkTS 的适配规则(5)
- 16-从 TypeScript 到 ArkTS 的适配规则(6)
- 从0到1上架一个元服务的全流程
- 鸿蒙Flutter实战:11-使用 Flutter SDK 3.22.0
- 从零到智能:打造智能灯控应用
- 07-ArkTS语法入门(1)
- 如何完成挖孔屏的适配
- HarmonyOS ArkTS中视频播放Video组件实现竖屏到横屏切换
- 「Mac玩转仓颉内测版11」PTA刷题篇2 - L1-002 打印沙漏
- 「Mac畅玩鸿蒙与硬件 11」鸿蒙UI组件篇1 - Text和Button组件详解
- LazyForEach ArkTS中的性能加速器
- 「Mac玩转仓颉内测版20」PTA刷题篇11 - L1-011 A-B