智能客服
你问我答,随时在线为你解决问题
您当前浏览的HarmonyOS 5.0.0(API 12)文档归档不再维护,推荐您使用最新版本。详细请参考文档维护策略变更。
本文通过更多应用场景中的案例,提供在ArkTS语法规则下将TS代码适配成ArkTS代码的建议。各章以ArkTS语法规则英文名称命名,每个案例提供适配前的TS代码和适配后的ArkTS代码。
应用代码
- interface W {
- bundleName: string
- action: string
- entities: string[]
- }
-
- let wantInfo: W = {
- 'bundleName': 'com.huawei.hmos.browser',
- 'action': 'ohos.want.action.viewData',
- 'entities': ['entity.system.browsable']
- }
建议改法
- interface W {
- bundleName: string
- action: string
- entities: string[]
- }
-
- let wantInfo: W = {
- bundleName: 'com.huawei.hmos.browser',
- action: 'ohos.want.action.viewData',
- entities: ['entity.system.browsable']
- }
- function printObj(obj: any) {
- console.log(obj);
- }
-
- printObj('abc');
建议改法
- function printObj(obj: string) {
- console.log(obj);
- }
-
- printObj('abc');
应用代码
- class A {
- v: number = 0
- s: string = ''
-
- foo(str: string) {
- let tmpStr = JSON.parse(str);
- if (tmpStr.add != undefined) {
- this.v = tmpStr.v;
- this.s = tmpStr.s;
- }
- }
- }
建议改法
- class A {
- v: number = 0
- s: string = ''
-
- foo(str: string) {
- let tmpStr: Record<string, Object> = JSON.parse(str);
- if (tmpStr.add != undefined) {
- this.v = tmpStr.v as number;
- this.s = tmpStr.s as string;
- }
- }
- }
应用代码
- function printProperties(obj: any) {
- console.log(obj.name);
- console.log(obj.value);
- }
建议改法
- function printProperties(obj: Record<string, Object>) {
- console.log(obj.name as string);
- console.log(obj.value as string);
- }
使用函数类型来替代。
应用代码
- interface I {
- (value: string): void;
- }
-
- function foo(fn: I) {
- fn('abc');
- }
-
- foo((value: string) => {
- console.log(value);
- })
建议改法
- type I = (value: string) => void
-
- function foo(fn: I) {
- fn('abc');
- }
-
- foo((value: string) => {
- console.log(value);
- })
应用代码
- class Controller {
- value: string = ''
-
- constructor(value: string) {
- this.value = value;
- }
- }
-
- type ControllerConstructor = {
- new (value: string): Controller;
- }
-
- class Menu {
- controller: ControllerConstructor = Controller
- createController() {
- if (this.controller) {
- return new this.controller(123);
- }
- return null;
- }
- }
-
- let t = new Menu();
- console.log(t.createController()!.value);
建议改法
- class Controller {
- value: string = ''
-
- constructor(value: string) {
- this.value = value;
- }
- }
-
- type ControllerConstructor = () => Controller;
-
- class Menu {
- controller: ControllerConstructor = () => {
- return new Controller('abc');
- }
-
- createController() {
- if (this.controller) {
- return this.controller();
- }
- return null;
- }
- }
-
- let t: Menu = new Menu();
- console.log(t.createController()!.value);
使用Record类型来替代。
应用代码
- function foo(data: { [key: string]: string }) {
- data['a'] = 'a';
- data['b'] = 'b';
- data['c'] = 'c';
- }
建议改法
- function foo(data: Record<string, string>) {
- data['a'] = 'a';
- data['b'] = 'b';
- data['c'] = 'c';
- }
应用代码
- class C {
- getInstance(): this {
- return this;
- }
- }
建议改法
- class C {
- getInstance(): C {
- return this;
- }
- }
应用代码
- class Person {
- constructor(readonly name: string) {}
-
- getName(): string {
- return this.name;
- }
- }
建议改法
- class Person {
- name: string
- constructor(name: string) {
- this.name = name;
- }
-
- getName(): string {
- return this.name;
- }
- }
应用代码
- class Controller {
- value: string = ''
-
- constructor(value: string) {
- this.value = value;
- }
- }
-
- interface ControllerConstructor {
- new (value: string): Controller;
- }
-
- class Menu {
- controller: ControllerConstructor = Controller
- createController() {
- if (this.controller) {
- return new this.controller('abc');
- }
- return null;
- }
- }
-
- let t = new Menu();
- console.log(t.createController()!.value);
建议改法
- class Controller {
- value: string = ''
-
- constructor(value: string) {
- this.value = value;
- }
- }
-
- type ControllerConstructor = () => Controller;
-
- class Menu {
- controller: ControllerConstructor = () => {
- return new Controller('abc');
- }
-
- createController() {
- if (this.controller) {
- return this.controller();
- }
- return null;
- }
- }
-
- let t: Menu = new Menu();
- console.log(t.createController()!.value);
可以转换成Record类型,用来访问对象的属性。
应用代码
- import { router } from '@kit.ArkUI';
- let params: Object = router.getParams();
- let funNum: number = params['funNum'];
- let target: string = params['target'];
建议改法
- import { router } from '@kit.ArkUI';
- let params = router.getParams() as Record<string, string | number>;
- let funNum: number = params.funNum as number;
- let target: string = params.target as string;
应用代码
- class A {
- str: string = ''
- }
- class B extends A {}
- class C extends A {}
-
- let arr: Array<A> = [];
-
- let originMenusMap:Map<string, C> = new Map(arr.map(item => [item.str, (item instanceof C) ? item: null]));
建议改法
- class A {
- str: string = ''
- }
- class B extends A {}
- class C extends A {}
-
- let arr: Array<A> = [];
-
- let originMenusMap: Map<string, C | null> = new Map<string, C | null>(arr.map<[string, C | null]>(item => [item.str, (item instanceof C) ? item: null]));
原因
(item instanceof C) ? item: null 需要声明类型为C | null,由于编译器无法推导出map的泛型类型参数,需要显式标注。
应用代码
- let regex: RegExp = /\s*/g;
建议改法
- let regexp: RegExp = new RegExp('\\s*','g');
原因
如果正则表达式中使用了标志符,需要将其作为new RegExp()的参数。
应用代码
- const area = {
- pixels: new ArrayBuffer(8),
- offset: 0,
- stride: 8,
- region: { size: { height: 1,width:2 }, x: 0, y: 0 }
- }
建议改法
- import { image } from '@kit.ImageKit';
-
- const area: image.PositionArea = {
- pixels: new ArrayBuffer(8),
- offset: 0,
- stride: 8,
- region: { size: { height: 1, width: 2 }, x: 0, y: 0 }
- }
应用代码
- class Test {
- value: number = 1
-
- constructor(value: number) {
- this.value = value;
- }
- }
-
- let t: Test = { value: 2 };
建议改法1
- // 去除构造函数
- class Test {
- value: number = 1
- }
-
- let t: Test = { value: 2 };
建议改法2
- // 使用new
- class Test {
- value: number = 1
-
- constructor(value: number) {
- this.value = value;
- }
- }
-
- let t: Test = new Test(2);
原因
- class C {
- value: number = 1
-
- constructor(n: number) {
- if (n < 0) {
- throw new Error('Negative');
- }
- this.value = n;
- }
- }
-
- let s: C = new C(-2); //抛出异常
- let t: C = { value: -2 }; //ArkTS不支持
例如在上面的例子中,如果允许使用C来标注object literal的类型,那么上述代码中的变量t会导致行为的二义性。ArkTS禁止通过object literal来绕过这一行为。
应用代码
- class Test {
- value: number = 0
- }
-
- let arr: Test[] = [
- {
- 'value': 1
- },
- {
- 'value': 2
- },
- {
- 'value': 3
- }
- ]
建议改法
- class Test {
- value: number = 0
- }
- let arr: Test[] = [
- {
- value: 1
- },
- {
- value: 2
- },
- {
- value: 3
- }
- ]
应用代码
- let obj: Record<string, number | string> = {
- value: 123,
- name: 'abc'
- }
建议改法
- let obj: Record<string, number | string> = {
- 'value': 123,
- 'name': 'abc'
- }
应用代码
- function foo(obj: { [key: string]: string}): string {
- if (obj != undefined && obj != null) {
- return obj.value1 + obj.value2;
- }
- return '';
- }
建议改法
- function foo(obj: Record<string, string>): string {
- if (obj != undefined && obj != null) {
- return obj.value1 + obj.value2;
- }
- return '';
- }
应用代码
- (fn) => {
- fn({ value: 123, name:'' });
- }
建议改法
- class T {
- value: number = 0
- name: string = ''
- }
-
- (fn: (v: T) => void) => {
- fn({ value: 123, name: '' });
- }
应用代码
- interface T {
- foo(value: number): number
- }
-
- let t:T = { foo: (value) => { return value } };
建议改法1
- interface T {
- foo: (value: number) => number
- }
-
- let t:T = { foo: (value) => { return value } };
建议改法2
- class T {
- foo: (value: number) => number = (value: number) => {
- return value;
- }
- }
-
- let t:T = new T();
原因
class/interface中声明的方法应该被所有class的实例共享。ArkTS不支持通过object literal改写实例方法。ArkTS支持函数类型的属性。
应用代码
- export default {
- onCreate() {
- // ...
- },
- onDestroy() {
- // ...
- }
- }
建议改法
- class Test {
- onCreate() {
- // ...
- }
- onDestroy() {
- // ...
- }
- }
-
- export default new Test()
应用代码
- // test.d.ets
- declare namespace test {
- interface I {
- id: string;
- type: number;
- }
-
- function foo(name: string, option: I): void;
- }
-
- export default test;
-
- // app.ets
- import { test } from 'test';
-
- let option = { id: '', type: 0 };
- test.foo('', option);
建议改法
- // test.d.ets
- declare namespace test {
- interface I {
- id: string;
- type: number;
- }
-
- function foo(name: string, option: I): void;
- }
-
- export default test;
-
- // app.ets
- import { test } from 'test';
-
- let option: test.I = { id: '', type: 0 };
- test.foo('', option);
原因
对象字面量缺少类型,根据test.foo分析可以得知,option的类型来源于声明文件,那么只需要将类型导入即可。
注意到在test.d.ets中,I是定义在namespace中的,所以在ets文件中,先导入namespace,再通过名称获取相应的类型。
应用代码
- function emit(event: string, ...args: Object[]): void {}
-
- emit('', {
- 'action': 11,
- 'outers': false
- });
建议改法
- function emit(event: string, ...args: Object[]): void {}
-
- let emitArg: Record<string, number | boolean> = {
- 'action': 11,
- 'outers': false
- }
-
- emit('', emitArg);
应用代码
- type Person = { name: string, age: number }
建议改法
- interface Person {
- name: string,
- age: number
- }
应用代码
- let permissionList = [
- { name: '设备信息', value: '用于分析设备的续航、通话、上网、SIM卡故障等' },
- { name: '麦克风', value: '用于反馈问题单时增加语音' },
- { name: '存储', value: '用于反馈问题单时增加本地文件附件' }
- ]
建议改法
为对象字面量声明类型
- class PermissionItem {
- name?: string
- value?: string
- }
-
- let permissionList: PermissionItem[] = [
- { name: '设备信息', value: '用于分析设备的续航、通话、上网、SIM卡故障等' },
- { name: '麦克风', value: '用于反馈问题单时增加语音' },
- { name: '存储', value: '用于反馈问题单时增加本地文件附件' }
- ]
应用代码
- class C {
- add(left: number, right: number): number {
- return left + right;
- }
- }
-
- function sub(left: number, right: number): number {
- return left - right;
- }
-
- let c1 = new C();
- c1.add = sub;
建议改法
- class C {
- add: (left: number, right: number) => number =
- (left: number, right: number) => {
- return left + right;
- }
- }
-
- function sub(left: number, right: number): number {
- return left - right;
- }
-
- let c1 = new C();
- c1.add = sub;
应用代码
- let a = +'5';
- let b = -'5';
- let c = ~'5';
- let d = +'string';
建议改法
- let a = Number.parseInt('5');
- let b = -Number.parseInt('5');
- let c = ~Number.parseInt('5');
- let d = new Number('string');
应用代码
- // module1.ts
- class C {
- value: number = 0
- }
-
- export let c = new C()
-
- // module2.ts
- import { c } from './module1'
- let t: typeof c = { value: 123 };
建议改法
- // module1.ts
- class C {
- value: number = 0
- }
-
- export { C }
-
- // module2.ts
- import { C } from './module1'
- let t: C = { value: 123 };
应用代码
- function test(str: string, obj: Record<string, Object>) {
- return str in obj;
- }
建议改法
- function test(str: string, obj: Record<string, Object>) {
- for (let i of Object.keys(obj)) {
- if (i == str) {
- return true;
- }
- }
- return false;
- }
应用代码
- let map = new Map<string, string>([['a', 'a'], ['b', 'b']]);
- for (let [key, value] of map) {
- console.log(key);
- console.log(value);
- }
建议改法
使用数组
- let map = new Map<string, string>([['a', 'a'], ['b', 'b']]);
- for (let arr of map) {
- let key = arr[0];
- let value = arr[1];
- console.log(key);
- console.log(value);
- }
应用代码
- import { BusinessError } from '@kit.BasicServicesKit'
-
- try {
- // ...
- } catch (e: BusinessError) {
- console.error(e.message, e.code);
- }
建议改法
- import { BusinessError } from '@kit.BasicServicesKit'
-
- try {
- // ...
- } catch (error) {
- let e: BusinessError = error as BusinessError;
- console.error(e.message, e.code);
- }
应用代码
- interface Person {
- [name: string]: string
- }
- let p: Person = {
- name: 'tom',
- age: '18'
- };
-
- for (let t in p) {
- console.log(p[t]); // log: "tom", "18"
- }
建议改法
- let p: Record<string, string> = {
- 'name': 'tom',
- 'age': '18'
- };
-
- for (let ele of Object.entries(p)) {
- console.log(ele[1]); // log: "tom", "18"
- }
应用代码
- class C {
- a: number = 0
- b: number = 0
- c: number = 0
- }
- type OptionsFlags = {
- [Property in keyof C]: string
- }
建议改法
- class C {
- a: number = 0
- b: number = 0
- c: number = 0
- }
-
- type OptionsFlags = Record<keyof C, string>
应用代码
- import { BusinessError } from '@kit.BasicServicesKit'
-
- function ThrowError(error: BusinessError) {
- throw error;
- }
建议改法
- import { BusinessError } from '@kit.BasicServicesKit'
-
- function ThrowError(error: BusinessError) {
- throw error as Error;
- }
原因
throw语句中值的类型必须为Error或者其继承类,如果继承类是一个泛型,会有编译期报错。建议使用as将类型转换为Error。
应用代码
- function foo() {
- console.log(this.value);
- }
-
- let obj = { value: 'abc' };
- foo.apply(obj);
建议改法1
使用类的方法实现,如果该方法被多个类使用,可以考虑采用继承的机制
- class Test {
- value: string = ''
- constructor (value: string) {
- this.value = value
- }
-
- foo() {
- console.log(this.value);
- }
- }
-
- let obj: Test = new Test('abc');
- obj.foo();
建议改法2
将this作为参数传入
- function foo(obj: Test) {
- console.log(obj.value);
- }
-
- class Test {
- value: string = ''
- }
-
- let obj: Test = { value: 'abc' };
- foo(obj);
建议改法3
将属性作为参数传入
- function foo(value: string) {
- console.log(value);
- }
-
- class Test {
- value: string = ''
- }
-
- let obj: Test = { value: 'abc' };
- foo(obj.value);
应用代码
- class Test {
- static value: number = 123
- static foo(): number {
- return this.value
- }
- }
建议改法
- class Test {
- static value: number = 123
- static foo(): number {
- return Test.value
- }
- }
应用代码
- // test.d.ets
- declare namespace test {
- interface I {
- id: string;
- type: number;
- }
-
- function foo(): I;
- }
-
- export default test
-
- // app.ets
- import test from 'test';
-
- let t: test.I = {
- ...test.foo(),
- type: 0
- }
建议改法
- // test.d.ets
- declare namespace test {
- interface I {
- id: string;
- type: number;
- }
-
- function foo(): I;
- }
-
- export default test
-
- // app.ets
- import test from 'test';
-
- let t: test.I = test.foo();
- t.type = 0;
原因
ArkTS中,对象布局在编译期是确定的。如果需要将一个对象的所有属性展开赋值给另一个对象可以通过逐个属性赋值语句完成。在本例中,需要展开的对象和赋值的目标对象类型恰好相同,可以通过改变该对象属性的方式重构代码。
在class内声明属性,而不是在构造函数上。
应用代码
- class Controller {
- value: string = ''
- constructor(value: string) {
- this.value = value
- }
- }
-
- type ControllerConstructor = new (value: string) => Controller;
-
- class Menu {
- controller: ControllerConstructor = Controller
- createController() {
- if (this.controller) {
- return new this.controller('abc');
- }
- return null;
- }
- }
-
- let t = new Menu()
- console.log(t.createController()!.value)
建议改法
- class Controller {
- value: string = ''
- constructor(value: string) {
- this.value = value;
- }
- }
-
- type ControllerConstructor = () => Controller;
-
- class Menu {
- controller: ControllerConstructor = () => { return new Controller('abc') }
- createController() {
- if (this.controller) {
- return this.controller();
- }
- return null;
- }
- }
-
- let t: Menu = new Menu();
- console.log(t.createController()!.value);
由于无法为globalThis添加静态类型,只能通过查找的方式访问globalThis的属性,造成额外的性能开销。另外,无法为globalThis的属性标记类型,无法保证对这些属性操作的安全和高性能。因此ArkTS不支持globalThis。
建议按照业务逻辑根据import/export语法实现数据在不同模块的传递。
必要情况下,可以通过构造的单例对象来实现全局对象的功能。(说明: 不能在har中定义单例对象,har在打包时会在不同的hap中打包两份,无法实现单例。)
构造单例对象
- // 构造单例对象
- export class GlobalContext {
- private constructor() {}
- private static instance: GlobalContext;
- private _objects = new Map<string, Object>();
-
- public static getContext(): GlobalContext {
- if (!GlobalContext.instance) {
- GlobalContext.instance = new GlobalContext();
- }
- return GlobalContext.instance;
- }
-
- getObject(value: string): Object | undefined {
- return this._objects.get(value);
- }
-
- setObject(key: string, objectClass: Object): void {
- this._objects.set(key, objectClass);
- }
- }
应用代码
- // file1.ts
-
- export class Test {
- value: string = '';
- foo(): void {
- globalThis.value = this.value;
- }
- }
-
- // file2.ts
-
- globalThis.value;
建议改法
- // file1.ts
-
- import { GlobalContext } from '../GlobalContext'
-
- export class Test {
- value: string = '';
- foo(): void {
- GlobalContext.getContext().setObject('value', this.value);
- }
- }
-
- // file2.ts
-
- import { GlobalContext } from '../GlobalContext'
-
- GlobalContext.getContext().getObject('value');
应用代码
- let arr: number[] = [1, 2, 3, 4];
- let str = String.fromCharCode.apply(null, Array.from(arr));
建议改法
- let arr: number[] = [1, 2, 3, 4];
- let str = String.fromCharCode(...Array.from(arr));
应用代码
- class A {
- value: string = ''
- foo: Function = () => {}
- }
-
- class Test {
- value: string = '1234'
- obj: A = {
- value: this.value,
- foo: this.foo.bind(this)
- }
-
- foo() {
- console.log(this.value);
- }
- }
建议改法1
- class A {
- value: string = ''
- foo: Function = () => {}
- }
-
- class Test {
- value: string = '1234'
- obj: A = {
- value: this.value,
- foo: (): void => this.foo()
- }
-
- foo() {
- console.log(this.value);
- }
- }
建议改法2
- class A {
- value: string = ''
- foo: Function = () => {}
- }
-
- class Test {
- value: string = '1234'
- foo: () => void = () => {
- console.log(this.value);
- }
- obj: A = {
- value: this.value,
- foo: this.foo
- }
- }
应用代码
- class A {
- value: string;
- constructor (value: string) {
- this.value = value;
- }
-
- foo() {
- console.log(this.value);
- }
- }
-
- let a1 = new A('1');
- let a2 = new A('2');
-
- a1.foo();
- a1.foo.apply(a2);
建议改法
- class A {
- value: string;
- constructor (value: string) {
- this.value = value;
- }
-
- foo() {
- this.fooApply(this);
- }
-
- fooApply(a: A) {
- console.log(a.value);
- }
- }
-
- let a1 = new A('1');
- let a2 = new A('2');
-
- a1.foo();
- a1.fooApply(a2);
应用代码
- let entries = new Map([
- ['foo', 123],
- ['bar', 456]
- ]);
-
- let obj = Object.fromEntries(entries);
建议改法
- let entries = new Map([
- ['foo', 123],
- ['bar', 456]
- ]);
-
- let obj: Record<string, Object> = {};
- entries.forEach((value, key) => {
- if (key != undefined && key != null) {
- obj[key] = value;
- }
- })
ArkTS不允许使用全局对象的属性和方法: Infinity, NaN, isFinite, isNaN, parseFloat, parseInt
可以使用Number的属性和方法: Infinity, NaN, isFinite, isNaN, parseFloat, parseInt
应用代码
- NaN;
- isFinite(123);
- parseInt('123');
建议改法
- Number.NaN;
- Number.isFinite(123);
- Number.parseInt('123');
应用代码
- interface I {
- name:string
- }
-
- class A {}
-
- class Test {
- a: number;
- b: string;
- c: boolean;
- d: I;
- e: A;
- }
建议改法
- interface I {
- name:string
- }
-
- class A {}
-
- class Test {
- a: number;
- b: string;
- c: boolean;
- d: I = { name:'abc' };
- e: A | null = null;
- constructor(a:number, b:string, c:boolean) {
- this.a = a;
- this.b = b;
- this.c = c;
- }
- }
应用代码
- class A {
- bar() {}
- }
- function foo(n: number) {
- if (n === 0) {
- return null;
- }
- return new A();
- }
- function getNumber() {
- return 5;
- }
- let a:A = foo(getNumber());
- a.bar();
建议改法
- class A {
- bar() {}
- }
- function foo(n: number) {
- if (n === 0) {
- return null;
- }
- return new A();
- }
- function getNumber() {
- return 5;
- }
-
- let a: A | null = foo(getNumber());
- a?.bar();
在class中,如果一个属性没有初始化,且没有在构造函数中被赋值,那么ArkTS将报错。
建议改法
1.一般情况下,建议按照业务逻辑在声明时初始化属性,或者在构造函数中为属性赋值。如:
- //code with error
- class Test {
- value: number
- flag: boolean
- }
-
- //方式一,在声明时初始化
- class Test {
- value: number = 0
- flag: boolean = false
- }
-
- //方式二,在构造函数中赋值
- class Test {
- value: number
- flag: boolean
- constructor(value: number, flag: boolean) {
- this.value = value;
- this.flag = flag;
- }
- }
2.对于对象类型(包括函数类型)A,如果不确定如何初始化,建议按照以下方式之一进行初始化
方式(i) prop: A | null = null
方式(ii) prop?: A
方式三(iii) prop: A | undefined = undefined
应用代码
- function foo(fn: (value?: string) => void, value: string): void {}
-
- foo((value: string) => {}, ''); //error
建议改法
- function foo(fn: (value?: string) => void, value: string): void {}
-
- foo((value?: string) => {}, '');
原因
例如,在以下的例子中,如果编译期不开启严格函数类型的检查,那么该段代码可以编译通过,但是在运行时会产生非预期的行为。具体来看,在foo的函数体中,一个undefined被传入fn(这是可以的,因为fn可以接受undefined),但是在代码第6行foo的调用点,传入的(value: string) => { console.log(value.toUpperCase()) }的函数实现中,始终将参数value当做string类型,允许其调用toUpperCase方法。如果不开启严格函数类型的检查,那么这段代码在运行时,会出现在undefined上无法找到属性的错误。
- function foo(fn: (value?: string) => void, value: string): void {
- let v: string | undefined = undefined;
- fn(v);
- }
-
- foo((value: string) => { console.log(value.toUpperCase()) }, ''); // Cannot read properties of undefined (reading 'toUpperCase')
为了避免运行时的非预期行为,如果在编译时开启了严格类型检查,这段代码将编译不通过,从而可以提醒开发者修改代码,保证程序安全。
应用代码
- class Test {
- private value?: string
-
- public printValue () {
- console.log(this.value.toLowerCase());
- }
- }
-
- let t = new Test();
- t.printValue();
建议改法
在编写代码时,建议减少可空类型的使用。如果对变量、属性标记了可空类型,那么在使用它们之间,需要进行空值的判断,根据是否为空值处理不同的逻辑。
- class Test {
- private value?: string
-
- public printValue () {
- if (this.value) {
- console.log(this.value.toLowerCase());
- }
- }
- }
-
- let t = new Test();
- t.printValue();
原因
在第一段代码中,如果编译期不开启严格空值检查,那么该段代码可以编译通过,但是在运行时会产生非预期的行为。这是因为t的属性value为undefined(这是因为value?: string是value: string | undefined = undefined的语法糖),在第11行调用printValue方法时,由于在该方法体内未对this.value的值进行空值检查,而直接按照string类型访问其属性,这就导致了运行时的错误。为了避免运行时的非预期行为,如果在编译时开起来严格空值检查,这段代码将编译不通过从而可以提醒开发者修改代码(如按照第二段代码的方式),保证程序安全。
应用代码
- class Test {
- handleClick: (action: string, externInfo?: string) => void | null = null;
- }
建议改法
在这种写法下,函数返回类型被解析为 void | undefined,需要添加括号用来区分union类型。
- class Test {
- handleClick: ((action: string, externInfo?: string) => void) | null = null;
- }
应用代码
- try {
-
- } catch (error) {
- console.log(error.message);
- }
建议改法
- import { BusinessError } from '@kit.BasicServicesKit'
-
- try {
-
- } catch (error) {
- console.log((error as BusinessError).message);
- }
应用代码
- class A {
- value: number
- constructor(value: number) {
- this.value = value;
- }
- }
-
- function foo(v: number): A | null {
- if (v > 0) {
- return new A(v);
- }
- return null;
- }
-
- let a: A = foo();
建议改法1
修改变量a的类型:let a: A | null = foo()。
- class A {
- value: number
- constructor(value: number) {
- this.value = value;
- }
- }
-
- function foo(v: number): A | null {
- if (v > 0) {
- return new A(v);
- }
- return null;
- }
-
- let a: A | null = foo(123);
-
- if (a != null) {
- // 非空分支
- } else {
- // 处理null
- }
建议改法2
如果可以断定此处调用foo一定返回非空值,可以使用非空断言!。
- class A {
- value: number
- constructor(value: number) {
- this.value = value;
- }
- }
-
- function foo(v: number): A | null {
- if (v > 0) {
- return new A(v);
- }
- return null;
- }
-
- let a: A = foo(123)!;
应用代码
- interface A {
- foo?: () => void
- }
-
- let a:A = { foo: () => {} };
- a.foo();
建议改法1
- interface A {
- foo: () => void
- }
- let a: A = { foo: () => {} };
- a.foo();
建议改法2
- interface A {
- foo?: () => void
- }
-
- let a: A = { foo: () => {} };
- if (a.foo) {
- a.foo();
- }
原因
在原先代码的定义中,foo是可选属性,有可能为undefined,对undefined的调用会导致报错。建议按照业务逻辑判断是否需要为可选属性。如果确实需要,那么在访问到该属性后需要进行空值检查。
应用代码
- class Test {
- value: number = 0
- }
-
- let a: Test
- try {
- a = { value: 1};
- } catch (e) {
- a.value;
- }
- a.value;
建议改法
- class Test {
- value: number = 0
- }
-
- let a: Test | null = null;
- try {
- a = { value:1 };
- } catch (e) {
- if (a) {
- a.value;
- }
- }
-
- if (a) {
- a.value;
- }
原因
对于primitive types,可以根据业务逻辑赋值,例如0,'',false。
对于对象类型,可以将类型修改为和null的联合类型,并赋值null,使用时需要进行非空检查。
应用代码
- function foo(a: number): number {
- if (a > 0) {
- return a;
- }
- }
建议改法1
根据业务逻辑,在else分支中返回合适的数值
建议改法2
- function foo(a: number): number | undefined {
- if (a > 0) {
- return a;
- }
- return
- }
应用代码
- // @ts-nocheck
- var a: any = 123;
建议改法
- let a: number = 123;
原因
ArkTS不支持通过注释的方式绕过严格类型检查。首先将注释(// @ts-nocheck或者// @ts-ignore)删去,再根据报错信息修改其他代码。
不允许.ts、.js文件import.ets文件源码。
建议改法
方式1.将.ts文件的后缀修改成ets,按照ArkTS语法规则适配代码。
方式2.将.ets文件中被.ts文件依赖的代码单独抽取到.ts文件中。
应用代码
- import type {A, B, C, D } from '***'
建议改法
- import {A, B, C, D } from '***'
应用代码
- class Controller {
- value: string = ''
- constructor(value: string) {
- this.value = value
- }
- }
-
- interface ControllerConstructor {
- new (value: string): Controller;
- }
-
- class Menu {
- controller: ControllerConstructor = Controller
- createController() {
- if (this.controller) {
- return new this.controller('abc');
- }
- return null;
- }
- }
-
- let t = new Menu();
- console.log(t.createController()!.value);
建议改法
- class Controller {
- value: string = ''
- constructor(value: string) {
- this.value = value
- }
- }
-
- type ControllerConstructor = () => Controller;
-
- class Menu {
- controller: ControllerConstructor = () => { return new Controller('abc'); }
- createController() {
- if (this.controller) {
- return this.controller();
- }
- return null;
- }
- }
-
- let t: Menu = new Menu();
- console.log(t.createController()!.value);
应用代码
- class C1 {
- static value: string = 'abc'
- }
-
- class C2 {
- static value: string = 'def'
- }
-
- function getValue(obj: any) {
- return obj['value'];
- }
-
- console.log(getValue(C1));
- console.log(getValue(C2));
建议改法
- class C1 {
- static value: string = 'abc'
- }
-
- class C2 {
- static value: string = 'def'
- }
-
- function getC1Value(): string {
- return C1.value;
- }
-
- function getC2Value(): string {
- return C2.value;
- }
-
- console.log(getC1Value());
- console.log(getC2Value());
改用动态import
应用代码
- import 'module'
建议改法
- import('module')
应用代码
- function foo(value: number): void {
- console.log(value.toString());
- }
-
- foo.add = (left: number, right: number) => {
- return left + right;
- }
-
- foo.sub = (left: number, right: number) => {
- return left - right;
- }
建议改法
- class Foo {
- static foo(value: number): void {
- console.log(value.toString());
- }
-
- static add(left: number, right: number): number {
- return left + right;
- }
-
- static sub(left: number, right: number): number {
- return left - right;
- }
- }
应用代码
- // lib.d.ts
- declare function foo(): any;
-
- // main.ets
- let e0: ESObject = foo();
-
- function f() {
- let e1 = foo();
- let e2: ESObject = 1;
- let e3: ESObject = {};
- let e4: ESObject = '';
- }
建议改法
- // lib.d.ts
- declare function foo(): any;
-
- // main.ets
- interface I {}
-
- function f() {
- let e0: ESObject = foo();
- let e1: ESObject = foo();
- let e2: number = 1;
- let e3: I = {};
- let e4: string = '';
- }
TypeScript
- function shallowCopy(obj: object): object {
- let newObj = {};
- Object.assign(newObj, obj);
- return newObj;
- }
ArkTS
- function shallowCopy(obj: object): object {
- let newObj: Record<string, Object> = {};
- for (let key of Object.keys(obj)) {
- newObj[key] = obj[key];
- }
- return newObj;
- }
TypeScript
- function deepCopy(obj: object): object {
- let newObj = Array.isArray(obj) ? [] : {};
- for (let key in obj) {
- if (typeof obj[key] === 'object') {
- newObj[key] = deepCopy(obj[key]);
- } else {
- newObj[key] = obj[key];
- }
- }
- return newObj;
- }
ArkTS
- function deepCopy(obj: object): object {
- let newObj: Record<string, Object> | Object[] = Array.isArray(obj) ? [] : {};
- for (let key of Object.keys(obj)) {
- if (typeof obj[key] === 'object') {
- newObj[key] = deepCopy(obj[key]);
- } else {
- newObj[key] = obj[key];
- }
- }
- return newObj;
- }
由于struct和class不同,不建议把this作为参数传递到struct外部使用,避免引起实例引用无法释放的情况,导致内存泄露。建议将状态变量对象传递到struct外面使用,通过修改对象的属性,来触发UI刷新。
不推荐用法
- export class MyComponentController {
- item: MyComponent = null;
-
- setItem(item: MyComponent) {
- this.item = item;
- }
-
- changeText(value: string) {
- this.item.value = value;
- }
- }
-
- @Component
- export default struct MyComponent {
- public controller: MyComponentController = null;
- @State value: string = 'Hello World';
-
- build() {
- Column() {
- Text(this.value)
- .fontSize(50)
- }
- }
-
- aboutToAppear() {
- if (this.controller)
- this.controller.setItem(this); // 不建议把this作为参数传递到struct外部使用
- }
- }
-
- @Entry
- @Component
- struct ObjThisOldPage {
- controller = new MyComponentController();
-
- build() {
- Column() {
- MyComponent({ controller: this.controller })
- Button('change value').onClick(() => {
- this.controller.changeText('Text');
- })
- }
- }
- }
推荐用法
- class CC {
- value: string = '1';
-
- constructor(value: string) {
- this.value = value;
- }
- }
-
- export class MyComponentController {
- item: CC = new CC('1');
-
- setItem(item: CC) {
- this.item = item;
- }
-
- changeText(value: string) {
- this.item.value = value;
- }
- }
-
- @Component
- export default struct MyComponent {
- public controller: MyComponentController | null = null;
- @State value: CC = new CC('Hello World');
-
- build() {
- Column() {
- Text(`${this.value.value}`)
- .fontSize(50)
- }
- }
-
- aboutToAppear() {
- if (this.controller)
- this.controller.setItem(this.value);
- }
- }
-
- @Entry
- @Component
- struct StyleExample {
- controller: MyComponentController = new MyComponentController();
-
- build() {
- Column() {
- MyComponent({ controller: this.controller })
- Button('change value').onClick(() => {
- this.controller.changeText('Text');
- })
- }
- }
- }
下面这段代码有arkts-no-any-unknown的报错,由于struct不支持泛型,建议使用联合类型,实现自定义组件类似泛型的功能。
不推荐用法
- class Data {
- aa: number = 11;
- }
-
- @Entry
- @Component
- struct DatauionOldPage {
- @State array: Data[] = [new Data(), new Data(), new Data()];
-
- @Builder
- componentCloser(data: Data) {
- Text(data.aa + '').fontSize(50)
- }
-
- build() {
- Row() {
- Column() {
- ForEachCom({ arrayList: this.array, closer: this.componentCloser })
- }
- .width('100%')
- }
- .height('100%')
- }
- }
-
- @Component
- export struct ForEachCom {
- arrayList: any[]; // struct不支持泛型,有arkts-no-any-unknown报错
- @BuilderParam closer: (data: any) => void = this.componentCloser; // struct不支持泛型,有arkts-no-any-unknown报错
-
- @Builder
- componentCloser() {
- }
-
- build() {
- Column() {
- ForEach(this.arrayList, (item: any) => { // struct不支持泛型,有arkts-no-any-unknown报错
- Row() {
- this.closer(item)
- }.width('100%').height(200).backgroundColor('#eee')
- })
- }
- }
- }
推荐用法
- class Data {
- aa: number = 11;
- }
-
- class Model {
- aa: string = '11';
- }
-
- type UnionData = Data | Model;
-
- @Entry
- @Component
- struct DatauionPage {
- array: UnionData[] = [new Data(), new Data(), new Data()];
-
- @Builder
- componentCloser(data: UnionData) {
- if (data instanceof Data) {
- Text(data.aa + '').fontSize(50)
- }
- }
-
- build() {
- Row() {
- Column() {
- ForEachCom({ arrayList: this.array, closer: this.componentCloser })
- }
- .width('100%')
- }
- .height('100%')
- }
- }
-
- @Component
- export struct ForEachCom {
- arrayList: UnionData[] = [new Data(), new Data(), new Data()];
- @BuilderParam closer: (data: UnionData) => void = this.componentCloser;
-
- @Builder
- componentCloser() {
- }
-
- build() {
- Column() {
- ForEach(this.arrayList, (item: UnionData) => {
- Row() {
- this.closer(item)
- }.width('100%').height(200).backgroundColor('#eee')
- })
- }
- }
- }