适配指导案例

本文通过更多应用场景中的案例,提供在ArkTS语法规则下将TS代码适配成ArkTS代码的建议。各章以ArkTS语法规则英文名称命名,每个案例提供适配前的TS代码和适配后的ArkTS代码。

arkts-identifiers-as-prop-names

应用代码

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']
}

arkts-no-any-unknown

按照业务逻辑,将代码中的any, unknown改为具体的类型

function printObj(obj: any) {
  console.log(obj);
}

printObj('abc');

建议改法

function printObj(obj: string) {
  console.log(obj);
}

printObj('abc');

标注JSON.parse返回值类型

应用代码

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;
    }
  }
}

使用Record类型

应用代码

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);
}

arkts-no-call-signature

使用函数类型来替代。

应用代码

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);
})

arkts-no-ctor-signatures-type

应用代码

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);

arkts-no-indexed-signatures

使用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';
}

arkts-no-typing-with-this

应用代码

class C {
  getInstance(): this {
    return this;
  }
}

建议改法

class C {
  getInstance(): C {
    return this;
  }
}

arkts-no-ctor-prop-decls

应用代码

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;
  }
}

arkts-no-ctor-signatures-iface

应用代码

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);

arkts-no-props-by-index

可以转换成Record类型,用来访问对象的属性。

应用代码

import myRouter from '@ohos.router';
let params: Object = myRouter.getParams();
let funNum: number = params['funNum'];
let target: string = params['target'];

建议改法

import myRouter from '@ohos.router';
let params = myRouter.getParams() as Record<string, string | number>;
let funNum: number = params.funNum as number;
let target: string = params.target as string;

arkts-no-inferred-generic-params

应用代码

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的泛型类型参数,需要显式标注。

arkts-no-regexp-literals

应用代码

let regex: RegExp = /\s*/g;

建议改法

let regexp: RegExp = new RegExp('\\s*','g');

原因

如果正则表达式中使用了标志符,需要将其作为new RegExp()的参数。

arkts-no-untyped-obj-literals

从SDK中导入类型,标注object literal类型

应用代码

const area = {
  pixels: new ArrayBuffer(8),
  offset: 0,
  stride: 8,
  region: { size: { height: 1,width:2 }, x: 0, y: 0 }
}

建议改法

import image from '@ohos.multimedia.image';

const area: image.PositionArea = {
  pixels: new ArrayBuffer(8),
  offset: 0,
  stride: 8,
  region: { size: { height: 1, width: 2 }, x: 0, y: 0 }
}

用class为object literal标注类型,需要class的构造函数无参数

应用代码

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/interface为object literal标注类型,需要使用identifier作为object literal的key

应用代码

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
  }
]

使用Record为object literal标注类型,需要使用字符串作为object literal的key

应用代码

let obj: Record<string, number | string> = {
  value: 123,
  name: 'abc'
}

建议改法

let obj: Record<string, number | string> = {
  'value': 123,
  'name': 'abc'
}

函数参数类型包含index signature

应用代码

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 '';
}

函数实参使用了object literal

应用代码

(fn) => {
  fn({ value: 123, name:'' });
}

建议改法

class T {
  value: number = 0
  name: string = ''
}

(fn: (v: T) => void) => {
  fn({ value: 123, name: '' });
}

class/interface 中包含方法

应用代码

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对象

应用代码

import hilog from '@ohos.hilog'

export default {
  onCreate() {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Application onCreate');
  },
  onDestroy() {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Application onDestroy');
  }
}

建议改法

import hilog from '@ohos.hilog'

class Test {
  onCreate() {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Application onCreate');
  }
  onDestroy() {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Application onDestroy');
  }
}

export default new Test()

通过导入namespace获取类型

应用代码

import { BusinessError } from '@ohos.base';
import bluetooth from '@ohos.bluetooth';
let serverNumber = -1;
function serverSocket(code: BusinessError, num: number) {
  console.log('bluetooth error code: ' + code.code);
  if (code.code == 0) {
    console.log('bluetooth serverSocket Number: ' + num);
    serverNumber = num;
  }
}

let sppOption = { uuid: '', secure: false, type: 0 };
bluetooth.sppListen('', sppOption, serverSocket);

建议改法

import { BusinessError } from '@ohos.base';
import bluetooth from '@ohos.bluetooth';
let serverNumber = -1;
function serverSocket(code: BusinessError, num: number) {
  console.log('bluetooth error code: ' + code.code);
  if (code.code == 0) {
    console.log('bluetooth serverSocket Number: ' + num);
    serverNumber = num;
  }
}

let sppOption: bluetooth.SppOption = { uuid: '', secure: false, type: 0 };
bluetooth.sppListen('', sppOption, serverSocket);

原因

对象字面量缺少类型,根据bluetooth.sppListen分析可以得知,sppOption的类型来源于SDK,那么只需要将类型导入即可。 注意到在@ohos.bluetooth中,sppOption是定义在namespace中的,所以在ets文件中,先导入namespace,再通过名称获取相应的类型。

object literal传参给Object类型

应用代码

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);

arkts-no-obj-literals-as-types

应用代码

type Person = { name: string, age: number }

建议改法

interface Person {
  name: string,
  age: number
}

arkts-no-noninferrable-arr-literals

应用代码

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: '用于反馈问题单时增加本地文件附件' }
]

arkts-no-method-reassignment

应用代码

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;

arkts-no-polymorphic-unops

应用代码

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');

arkts-no-type-query

应用代码

// 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 };

arkts-no-in

使用Object.keys判断属性是否存在

应用代码

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;
}

arkts-no-destruct-assignment

应用代码

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);
}

arkts-no-types-in-catch

应用代码

import { BusinessError } from '@ohos.base';

try {
  // ...
} catch (e: BusinessError) {
  logger.error(e.code, e.message);
}

建议改法

import { BusinessError } from '@ohos.base';

try {
  // ...
} catch (error) {
  let e: BusinessError = error as BusinessError;
  logger.error(e.code, e.message);
}

arkts-no-for-in

应用代码

interface Person {
  [name: string]: string
}
let p: Person = {
  name: 'tom',
  age: '18'
};

for (let t in p) {
  console.log(p[t]);
}

建议改法

let p: Record<string, string> = {
  'name': 'tom',
  'age': '18'
};

for (let ele of Object.entries(p)) {
  console.log(ele[1]);
}

arkts-no-mapped-types

应用代码

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>

arkts-limited-throw

应用代码

import { BusinessError } from '@ohos.base';

function ThrowError(error: BusinessError) {
  throw error;
}

建议改法

import { BusinessError } from '@ohos.base';

function ThrowError(error: BusinessError) {
  throw error as Error;
}

原因

throw语句中值的类型必须为Error或者其继承类,如果继承类是一个泛型,会有编译期报错。建议使用as将类型转换为Error

arkts-no-standalone-this

函数内使用this

应用代码

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的静态方法内使用this

应用代码

class Test {
  static value: number = 123
  static foo(): number {
    return this.value
  }
}

建议改法

class Test {
  static value: number = 123
  static foo(): number {
    return Test.value
  }
}

arkts-no-spread

应用代码

import notification from '@ohos.notificationManager';

function buildNotifyLongRequest(): notification.NotificationRequest {
  // ...
}

let notificationRequest: notification.NotificationRequest = {
  ...buildNotifyLongRequest(),
  deliveryTime: new Date().getTime()
}

建议改法

import notification from '@ohos.notificationManager';

function buildNotifyLongRequest():notification.NotificationRequest {
    // ...
}

let notificationRequest: notification.NotificationRequest = buildNotifyLongRequest();
notificationRequest.deliveryTime = new Date().getTime();

原因

ArkTS中,对象布局在编译期是确定的。如果需要将一个对象的所有属性展开赋值给另一个对象可以通过逐个属性赋值语句完成。在本例中,需要展开的对象和赋值的目标对象类型恰好相同,可以通过改变该对象属性的方式重构代码。

arkts-no-ctor-signatures-funcs

在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);

arkts-no-globalthis

由于无法为globalThis添加静态类型,只能通过查找的方式访问globalThis的属性,造成额外的性能开销。另外,无法为globalThis的属性标记类型,无法保证对这些属性操作的安全和高性能。因此ArkTS不支持globalThis。

  1. 建议按照业务逻辑根据import/export语法实现数据在不同模块的传递。

  2. 必要情况下,可以通过构造的单例对象来实现全局对象的功能。(**说明:**不能在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');

arkts-no-func-apply-bind-call

使用标准库中接口

应用代码

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));

bind定义方法

应用代码

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
  }
}

使用apply

应用代码

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);

arkts-limited-stdlib

Object.fromEntries()

应用代码

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;
  }
})

使用Number的属性和方法

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');

arkts-strict-typing(StrictModeError)

strictPropertyInitialization

应用代码

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;
  }
}

Type *** | null is not assignable to type ***

应用代码

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

  • 从性能角度来说,null类型只用在编译期的类型检查中,对虚拟机的性能无影响。而undefined | A被视为联合类型,运行时可能有额外的开销。
  • 从代码可读性、简洁性的角度来说,prop?:Aprop: 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的属性valueundefined(这是因为value?: stringvalue: 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;
}

'***' is of type 'unknown'

应用代码

try {
  
} catch (error) {
  console.log(error.message);
}

建议改法

import { BusinessError } from '@ohos.base'

try {
  
} catch (error) {
  console.log((error as BusinessError).message);
}

Type '*** | null' is not assignable to type '***'

应用代码

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)!;

Cannot invoke an object which possibly 'undefined'

应用代码

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的调用会导致报错。建议按照业务逻辑判断是否需要为可选属性。如果确实需要,那么在访问到该属性后需要进行空值检查。

Variable '***' is used before being assigned

应用代码

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 lacks ending return statement and return type does not include 'undefined'.

应用代码

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
}

arkts-strict-typing-required

应用代码

// @ts-nocheck
var a: any = 123;

建议改法

let a: number = 123;

原因

ArkTS不支持通过注释的方式绕过严格类型检查。首先将注释(// @ts-nocheck或者// @ts-ignore)删去,再根据报错信息修改其他代码。

Importing ArkTS files to JS and TS files is not allowed

arkts-no-tsdeps

不允许.ts、.js文件import.ets文件源码。

建议改法

方式1.将.ts文件的后缀修改成ets,按照ArkTS语法规则适配代码。

方式2.将.ets文件中被.ts文件依赖的代码单独抽取到.ts文件中。

arkts-no-special-imports

应用代码

import type {A, B, C, D } from '***'

建议改法

import {A, B, C, D } from '***'

arkts-no-classes-as-obj

使用class构造实例

应用代码

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());

arkts-no-side-effects-imports

改用动态import

应用代码

import 'module'

建议改法

import('module')

arkts-no-func-props

应用代码

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;
  }
}

状态管理使用典型场景

Struct组件外使用状态变量

由于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);
  }
}

@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')
      })
    }
  }
}

Struct支持联合类型的方案

下面这段代码有arkts-no-any-unknown的报错,由于strcut不支持泛型,建议使用联合类型,实现自定义组件类似泛型的功能。

不推荐用法

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[]
  @BuilderParam closer: (data: any) => void = this.componentCloser

  @Builder
  componentCloser() {
  }

  build() {
    Column() {
      ForEach(this.arrayList, (item: any) => {
        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')
      })
    }
  }
}