harmony 鸿蒙Adaptation Cases

  • 2025-06-12
  • 浏览 (6)

Adaptation Cases

In this document, various use cases are presented to provide suggestions on adapting TS code to ArkTS for compliance with ArkTS syntax rules. Each chapter is named after an ArkTS syntax rule. Each use case provides the TS code before adaptation and the ArkTS code after adaptation.

arkts-identifiers-as-prop-names

Before adaptation

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

After adaptation

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

Changing any and unknown to Specific Types

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

printObj('abc');

After adaptation

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

printObj('abc');

Marking JSON.parse Return Value Type

Before adaptation

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

After adaptation

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

Using Record Type

Before adaptation

function printProperties(obj: any) {
  console.log(obj.name);
  console.log(obj.value);
}

After adaptation

function printProperties(obj: Record<string, Object>) {
  console.log(obj.name as string);
  console.log(obj.value as string);
}

arkts-no-call-signature

Use the function type instead.

Before adaptation

interface I {
  (value: string): void;
}

function foo(fn: I) {
  fn('abc');
}

foo((value: string) => {
  console.log(value);
})

After adaptation

type I = (value: string) => void

function foo(fn: I) {
  fn('abc');
}

foo((value: string) => {
  console.log(value);
})

arkts-no-ctor-signatures-type

Before adaptation

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

After adaptation

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

Use the Record type instead.

Before adaptation

function foo(data: { [key: string]: string }) {
  data['a'] = 'a';
  data['b'] = 'b';
  data['c'] = 'c';
}

After adaptation

function foo(data: Record<string, string>) {
  data['a'] = 'a';
  data['b'] = 'b';
  data['c'] = 'c';
}

arkts-no-typing-with-this

Before adaptation

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

After adaptation

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

arkts-no-ctor-prop-decls

Before adaptation

class Person {
  constructor(readonly name: string) {}

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

After adaptation

class Person {
  name: string
  constructor(name: string) {
    this.name = name;
  }

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

arkts-no-ctor-signatures-iface

Before adaptation

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

After adaptation

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

Use the Record type to access object attributes.

Before adaptation

import { router } from '@kit.ArkUI';
let params: Object = router.getParams();
let funNum: number = params['funNum'];
let target: string = params['target'];

After adaptation

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;

arkts-no-inferred-generic-params

Before adaptation

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

After adaptation

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

Reason for change

(item instanceof C) ? item: null needs to be declared as type C|null. This is because the compiler cannot deduce the generic type parameter of map, and explicit type annotation is required.

arkts-no-regexp-literals

Before adaptation

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

After adaptation

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

Reason for change

To include a flag in a regular expression, use it as a parameter of new RegExp().

arkts-no-untyped-obj-literals

Specifying Object Literal Type for Type Imports

Before adaptation

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

After adaptation

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

Using a Class for Object Literal Type Annotation Only When the Class Constructor Have No Parameters

Before adaptation

class Test {
  value: number = 1

  constructor(value: number) {
    this.value = value;
  }
}

let t: Test = { value: 2 };

After adaptation: mode 1

// Remove the constructor.
class Test {
  value: number = 1
}

let t: Test = { value: 2 };

After adaptation: mode 2

// Use new.
class Test {
  value: number = 1
  
  constructor(value: number) {
    this.value = value;
  }
}

let t: Test = new Test(2);

Reason for change

class C {
  value: number = 1
  
  constructor(n: number) {
    if (n < 0) {
      throw new Error('Negative');
    }
    this.value = n;
  }
}

let s: C = new C(-2); 	// An exception is thrown.
let t: C = { value: -2 };	// Not supported by ArkTS.

In the preceding example, if C is allowed to be used to specify the object literal type, the variable t in the code will cause ambiguity of behavior. In light of this, ArkTS does not allow for object literal type annotation that may cause this issue.

Using an Identifier as the Object Literal Key When Specifying the Object Literal Type with a Class or Interface

Before adaptation

class Test {
  value: number = 0
}

let arr: Test[] = [
  {
    'value': 1
  },
  {
    'value': 2
  },
  {
    'value': 3
  }
]

After adaptation

class Test {
  value: number = 0
}
let arr: Test[] = [
  {
    value: 1
  },
  {
    value: 2
  },
  {
    value: 3
  }
]

If Record is used to specify the object literal type, a string must be used as the key of the object literal.

Before adaptation

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

After adaptation

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

Including Index Signature in the Function Parameter Type

Before adaptation

function foo(obj: { [key: string]: string}): string {
  if (obj != undefined && obj != null) {
    return obj.value1 + obj.value2;
  }
  return '';
}

After adaptation

function foo(obj: Record<string, string>): string {
  if (obj != undefined && obj != null) {
    return obj.value1 + obj.value2;
  }
  return '';
}

Including Object Literals in Actual Parameters of Functions

Before adaptation

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

After adaptation

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

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

Including Methods in Classes or Interfaces

Before adaptation

interface T {
  foo(value: number): number
}

let t:T = { foo: (value) => { return value } };

After adaptation: mode 1

interface T {
  foo: (value: number) => number
}

let t:T = { foo: (value) => { return value } };

After adaptation: mode 2

class T {
  foo: (value: number) => number = (value: number) => {
    return value;
  }
}

let t:T = new T();

Reason for change

The methods declared in a class or interface should be shared by all instances of the class. In ArkTS, object literals cannot be used to rewrite instance methods. ArkTS supports attributes of the function type.

export default Object

Before adaptation

export default {
  onCreate() {
    // ...
  },
  onDestroy() {
    // ...
  }
}

After adaptation

class Test {
  onCreate() {
    // ...
  }
  onDestroy() {
    // ...
  }
}

export default new Test()

Obtaining the Type by Importing a Namespace

Before adaptation

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

After adaptation

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

Reason for change

The object literal lacks a type. According to the analysis of test.foo, the option type comes from the declaration file. Therefore, you only need to import the type. In test.d.ets, I is defined in the namespace. Therefore, to import the type in the .ets file, import the namespace and then obtain the target type based on the name.

Passing Parameters from the Object Literal to the Object Type

Before adaptation

function emit(event: string, ...args: Object[]): void {}

emit('', {
  'action': 11,
  'outers': false
});

After adaptation

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

Before adaptation

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

After adaptation

interface Person {
  name: string,
  age: number
}

arkts-no-noninferrable-arr-literals

Before adaptation

let permissionList = [
  {name: 'Device information', value: 'Used to analyze the battery life, call, Internet access, and SIM card issues of the device.'},
  {name: 'Microphone', value: 'Used to add voice when reporting an issue.'},
  {"name: 'Storage', value: 'Used to add local file attachments when reporting an issue.'}
]

After adaptation

Declare the type for the object literal.

class PermissionItem {
  name?: string
  value?: string
}

let permissionList: PermissionItem[] = [
  {name: 'Device information', value: 'Used to analyze the battery life, call, Internet access, and SIM card issues of the device.'},
  {name: 'Microphone', value: 'Used to add voice when reporting an issue.'},
  {"name: 'Storage', value: 'Used to add local file attachments when reporting an issue.'}
]

arkts-no-method-reassignment

Before adaptation

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;

After adaptation

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

Before adaptation

let a = +'5';
let b = -'5';
let c = ~'5';
let d = +'string';

After adaptation

let a = Number.parseInt('5');
let b = -Number.parseInt('5');
let c = ~Number.parseInt('5');
let d = new Number('string');

arkts-no-type-query

Before adaptation

// module1.ts
class C {
  value: number = 0
}

export let c = new C()

// module2.ts
import { c } from './module1'
let t: typeof c = { value: 123 };

After adaptation

// module1.ts
class C {
  value: number = 0
}

export { C }

// module2.ts
import { C } from './module1'
let t: C = { value: 123 };

arkts-no-in

Using Object.keys to Determine Whether an Attribute Exists

Before adaptation

function test(str: string, obj: Record<string, Object>) {
  return str in obj;
}

After adaptation

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

Before adaptation

let map = new Map<string, string>([['a', 'a'], ['b', 'b']]);
for (let [key, value] of map) {
  console.log(key);
  console.log(value);
}

After adaptation

Use arrays.

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

Before adaptation

import { BusinessError } from '@kit.BasicServicesKit'

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

After adaptation

import { BusinessError } from '@kit.BasicServicesKit'

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

arkts-no-for-in

Before adaptation

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

for (let t in p) {
  console.log(p[t]);  // log: "tom", "18" 
}

After adaptation

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

for (let ele of Object.entries(p)) {
  console.log(ele[1]);  // log: "tom", "18" 
}

arkts-no-mapped-types

Before adaptation

class C {
  a: number = 0
  b: number = 0
  c: number = 0
}
type OptionsFlags = {
  [Property in keyof C]: string
}

After adaptation

class C {
  a: number = 0
  b: number = 0
  c: number = 0
}

type OptionsFlags = Record<keyof C, string>

arkts-limited-throw

Before adaptation

import { BusinessError } from '@kit.BasicServicesKit'

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

After adaptation

import { BusinessError } from '@kit.BasicServicesKit'

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

Reason for change

The type of the value in the throw statement must be Error or its inheritance class. If the inheritance class is a generic, an error is reported during compilation. You are advised to use as to convert the type to Error.

arkts-no-standalone-this

Using this in a Function

Before adaptation

function foo() {
  console.log(this.value);
}

let obj = { value: 'abc' };
foo.apply(obj);

After adaptation: mode 1

Use the method of a class. If the method is used by multiple classes, consider using the inheritance mechanism.

class Test {
  value: string = ''
  constructor (value: string) {
    this.value = value
  }
  
  foo() {
    console.log(this.value);
  }
}

let obj: Test = new Test('abc');
obj.foo();

After adaptation: mode 2

Passing this as a Parameter

function foo(obj: Test) {
  console.log(obj.value);
}

class Test {
  value: string = ''
}

let obj: Test = { value: 'abc' };
foo(obj);

After adaptation: mode 3

Pass the attribute as a parameter.

function foo(value: string) {
  console.log(value);
}

class Test {
  value: string = ''
}

let obj: Test = { value: 'abc' };
foo(obj.value);

Using this in the Static Method of a Class

Before adaptation

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

After adaptation

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

arkts-no-spread

Before adaptation

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

After adaptation

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

Reason for change

In ArkTS, the object layout is determined at compile time. To assign all attributes of an object to another object, you can use the attribute-by-attribute assignment statement. In this example, the type of the source object s the same as that of the target object. In this case, you can reconstruct the code by changing the object attribute.

arkts-no-ctor-signatures-funcs

Declare attributes within a class, not on a constructor.

Before adaptation

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)

After adaptation

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

ArkTS does not support globalThis for two reasons:
(1) A static type cannot be added for globalThis. As a result, the attributes of globalThis can be accessed only through search, which causes extra performance overhead.
(2) Type annotation is not available for attributes of globalThis. As a result, the security and performance of operations on these attributes cannot be ensured.

  1. You are advised to transfer data between modules based on the service logic and import/export syntax.

  2. If necessary, you can construct a singleton object to implement the function of a global object. (NOTE: The singleton object cannot be defined in a HAR file, which packages two copies in different HAP files and therefore cannot implement singleton objects.)

Construct a singleton object.

// Construct a singleton object.
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);
  }
}

Before adaptation


// file1.ts

export class Test {
  value: string = '';
  foo(): void {
    globalThis.value = this.value;
  }
}

// file2.ts

globalThis.value;

After adaptation


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

Using Interfaces in the Standard Library

Before adaptation

let arr: number[] = [1, 2, 3, 4];
let str = String.fromCharCode.apply(null, Array.from(arr));

After adaptation

let arr: number[] = [1, 2, 3, 4];
let str = String.fromCharCode(...Array.from(arr));

Using bind in Method Definitions

Before adaptation

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

After adaptation: mode 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);
  }
}

After adaptation: mode 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
  }
}

Using apply

Before adaptation

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

After adaptation

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

Before adaptation

let entries = new Map([
  ['foo', 123],
  ['bar', 456]
]);

let obj = Object.fromEntries(entries);

After adaptation

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

Using Attributes and Methods of Number

ArkTS does not allow the use of the following attributes and methods for global objects: Infinity, NaN, isFinite, isNaN, parseFloat, and parseInt

You can use them for Number.

Before adaptation

NaN;
isFinite(123);
parseInt('123');

After adaptation

Number.NaN;
Number.isFinite(123);
Number.parseInt('123');

arkts-strict-typing(StrictModeError)

strictPropertyInitialization

Before adaptation

interface I {
  name:string
}

class A {}

class Test {
  a: number;
  b: string;
  c: boolean;
  d: I;
  e: A;
}

After adaptation

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

Before adaptation

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

After adaptation

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

Strict Attribute Initialization Check

In a class, if an attribute is not initialized and is not assigned a value in the constructor, ArkTS reports an error.

After adaptation

  1. Whenever possible, initialize attributes during declaration based on service logic or assign values to the attributes in constructors. Example:
//code with error
class Test {
  value: number
  flag: boolean
}

// Method 1: Initialize attributes during declaration.
class Test {
  value: number = 0
  flag: boolean = false
}

// Method 2: Assign values to attributes in the constructor.
class Test {
  value: number
  flag: boolean
  constructor(value: number, flag: boolean) {
    this.value = value;
    this.flag = flag;
  }
}
  1. For object type (including function type) A, if you are not sure how to initialize it, you are advised to initialize it in one of the following ways:

​ Mode (i): prop: A|null = null

​ Mode (ii): prop?:A

​ Mode 3 (iii): prop: A|undefined = undefined

  • From the perspective of performance, the null type is used only for type check during compilation and has no impact on VM performance. In contrast, undefined|A is treated as a union type and may result in additional overhead at run time.
  • In terms of code readability and simplicity, prop?:A is the syntax sugar of prop: A|undefined = undefined. You are advised to use optional attributes.

Strict Function Type Check

Before adaptation

function foo(fn: (value?: string) => void, value: string): void {}

foo((value: string) => {}, ''); //error

After adaptation

function foo(fn: (value?: string) => void, value: string): void {}

foo((value?: string) => {}, '');

Reason for change

In the following example, if strict function type check is not enabled during compilation, the code can be compiled successfully, but unexpected behavior occurs at run time. Specifically, in the function body of foo, an undefined is passed in to fn (this is acceptable because fn can accept undefined). However, at the invoking point of foo in line 6 of the code, in the passed function implementation of (value: string) => { console.log(value.toUpperCase()) }, the value parameter is always of the string type and can call the toUpperCase method. If strict function type check is not enabled, an error indicating that the property cannot be found on undefined occurs at run time.

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

If strict type check is enabled during compilation, the preceding issue can be detected at compile time.

Strict Null Check

Before adaptation

class Test {
  private value?: string
  
  public printValue () {
    console.log(this.value.toLowerCase());
  }
}

let t = new Test();
t.printValue();

After adaptation

When writing code, minimize the use of nullable types. If a variable or property is marked with a nullable type, a null check is required. Process the service logic based on whether the variable or property is null.

class Test {
  private value?: string

  public printValue () {
    if (this.value) {
      console.log(this.value.toLowerCase());
    }
  }
}

let t = new Test();
t.printValue();

Reason for change

In the first code segment, if strict null check is not enabled during compilation, the code segment can be compiled successfully, but unexpected behavior occurs at run time. This is because the value property of t is undefined (value?: string is the syntax sugar of value: string|undefined = undefined), and when the printValue method is called in line 11, the property is directly accessed based on the string type, due to a lack of null check on the value of this.value in the method body. To avoid unexpected behavior at run time, enable strict null check during compilation.

Function Return Type Mismatch

Before adaptation

class Test {
  handleClick: (action: string, externInfo?: string) => void|null = null;
}

After adaptation

In the original code, the return type of the function is parsed as void|undefined. Add parentheses to distinguish the union type.

class Test {
  handleClick: ((action: string, externInfo?: string) => void)|null = null;
}

’***’ is of type ‘unknown’

Before adaptation

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

After adaptation

import { BusinessError } from '@kit.BasicServicesKit'

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

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

Before adaptation

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

After adaptation: mode 1

Change the type of variable a to 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) {
  // Non-empty branch
} else {
  // Process null.
}

After adaptation: mode 2

If you can determine that a non-null value is returned when foo is called, you can use a non-null assertion operator !.

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’

Before adaptation

interface A {
  foo?: () => void
}

let a:A = { foo: () => {} };
a.foo();

After adaptation: mode 1

interface A {
  foo: () => void
}
let a: A = { foo: () => {} };
a.foo();

After adaptation: mode 2

interface A {
  foo?: () => void
}

let a: A = { foo: () => {} };
if (a.foo) {
  a.foo();
}

Reason for change

In the original code definition, foo is an optional property and may be undefined. If undefined is called, an error is reported. You are advised to determine whether a property is optional based on the service logic. If defining an optional property is necessary, a null check is required for accessing the property.

Variable ‘***’ is used before being assigned

Before adaptation

class Test {
  value: number = 0
}

let a: Test
try {
  a = { value: 1};
} catch (e) {
  a.value;
}
a.value;

After adaptation

class Test {
  value: number = 0
}

let a: Test|null = null;
try {
  a = { value:1 };
} catch (e) {
  if (a) {
    a.value;
  }
}

if (a) {
  a.value;
}

Reason for change

For primitive types, a value can be assigned based on the service logic, for example, 0, , and false.

For the object type, you can change the type to a union type consisting of null and assign null to the type. In this case, when using the object type, you need to perform the non-null check.

Function lacks ending return statement and return type does not include ‘undefined’.

Before adaptation

function foo(a: number): number {
  if (a > 0) {
    return a;
  }
}

After adaptation: mode 1

Return a proper value in the else branch based on the service logic.

After adaptation: mode 2

function foo(a: number): number|undefined {
  if (a > 0) {
    return a;
  }
  return
}

arkts-strict-typing-required

Before adaptation

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

After adaptation

let a: number = 123;

Reason for change

ArkTS does not support the use of comments to bypass strict type checks. Delete the comment (// @ts-nocheck or // @ts-ignore), and then modify other code based on the error information.

Importing ArkTS files to JS and TS files is not allowed

arkts-no-tsdeps

In .ts and .js files, it is not allowed to import source code from an .ets file.

After adaptation

Mode 1: Change the file name extension of the .ts file to .ets and adapt the code based on the ArkTS syntax rules.

Mode 2: Extract the code that the .ts file depends on from the .ets file to the .ts file.

arkts-no-special-imports

Before adaptation

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

After adaptation

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

arkts-no-classes-as-obj

Using Class to Construct an Instance

Before adaptation

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

After adaptation

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

Accessing Static Properties

Before adaptation

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

After adaptation

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

Use Dynamic Imports

Before adaptation

import 'module'

After adaptation

import('module')

arkts-no-func-props

Before adaptation

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

After adaptation

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

arkts-limited-esobj

Before adaptation

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

After adaptation

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

Copy

Shallow Copy

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

Deep Copy

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

Typical Application Scenarios of State Management

Using State Variables Outside of Structs

The struct is different from the class. Therefore, avoid passing this as a parameter to the outside of the struct. Otherwise, the instance reference cannot be released and memory leakage may occur. You are advised to pass the state variable object outside the struct and modify the object properties to trigger UI re-render.

Not recommended

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); // You are not advised to pass this as a parameter to the outside struct.
  }
}

@Entry
@Component
struct ObjThisOldPage {
  controller = new MyComponentController();

  build() {
    Column() {
      MyComponent({ controller: this.controller })
      Button('change value').onClick(() => {
        this.controller.changeText('Text');
      })
    }
  }
}

Recommended

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

Using Union Types in Structs

The following code contains the arkts-no-any-unknown error. Because the struct does not support generics, you are advised to use the union type to implement generic-like functions of custom components.

Not recommended

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[]; // The struct does not support generics. An arkts-no-any-unknown error is reported.
  @BuilderParam closer: (data: any) => void = this.componentCloser; // The struct does not support generics. An arkts-no-any-unknown error is reported.

  @Builder
  componentCloser() {
  }

  build() {
    Column() {
      ForEach(this.arrayList, (item: any) => { // The struct does not support generics. An arkts-no-any-unknown error is reported.
        Row() {
          this.closer(item)
        }.width('100%').height(200).backgroundColor('#eee')
      })
    }
  }
}

Recommended

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

你可能感兴趣的鸿蒙文章

harmony 鸿蒙Quick Start

harmony 鸿蒙Creating an Application Clone

harmony 鸿蒙app.json5 Configuration File

harmony 鸿蒙Structure of the app Tag

harmony 鸿蒙Overview of Application Configuration Files in FA Model

harmony 鸿蒙Overview of Application Configuration Files in Stage Model

harmony 鸿蒙Application Installation, Uninstall, and Update

harmony 鸿蒙Application Package Overview

harmony 鸿蒙Application Package Structure in FA Model

harmony 鸿蒙Application Package Structure in Stage Model

0  赞