Intelligent Assistant
Chat with our virtual assistant to get answers promptly.
Welcome to the tutorial for ArkTS, a TypeScript-based programming language designed specifically to build high-performance mobile applications! ArkTS is optimized to provide better performance and efficiency, while still maintaining the familiar syntax of TypeScript.
Many current programming languages were not designed with mobile devices in mind, resulting in slow and inefficient applications that drain battery life. As mobile devices continue to become more prevalent in our daily lives, there is a growing need for programming languages optimized for the mobile environment. ArkTS has been specifically designed to address such concerns by prioritizing higher execution efficiency.
ArkTS is based on the popular programming language TypeScript that extends JavaScript by adding type definitions. TypeScript is well-loved by many developers as it provides a more structured approach to coding in JavaScript. ArkTS aims to keep the look and feel of TypeScript to enable a seamless transition for the existing TypeScript developers, and to let mobile developers learn ArkTS quickly.
One of the key features of ArkTS is its focus on low runtime overhead. ArkTS imposes stricter limitations on the TypeScript's dynamically typed features, reducing runtime overhead and allowing faster execution. By eliminating the dynamically typed features from the language, ArkTS code can be compiled ahead-of-time more efficiently, resulting in faster application startup and lower power consumption.
Interoperability with TypeScript and JavaScript was a critical consideration in the ArkTS language design. Many mobile application developers already have TypeScript and JavaScript code and libraries they would want to reuse. ArkTS has been designed for seamless JavaScript interoperability, making it easy for the developers to integrate the JavaScript code into their applications and vice versa. This will allow the developers to use their existing codebases and libraries to leverage the power of our new language.
This tutorial will guide you through the core features, syntax, and best practices of ArkTS. After reading this tutorial through the end, you will be able to build performant and efficient mobile applications in ArkTS.
For a more detailed understanding of the ArkTS language, see Introduction to ArkTS
Importing Open Capabilities of the HarmonyOS SDK
The open capabilities (APIs) provided by the HarmonyOS SDK can be used after import declarations are made. You can directly import a module to use all APIs in the module. For example:
import UIAbility from '@ohos.app.ability.UIAbility';
The kit concept has been introduced since HarmonyOS NEXT Developer Preview 1. The SDK encapsulates modules of the same kit. You can import a kit to the sample code to use its APIs. For details about the modules encapsulated in a specific kit, see the definition of the kit in its respective subdirectory under the SDK directory.
You can import a kit in any of the following ways:
Method 1: Import a single module under a kit. For example:
import { UIAbility } from '@kit.AbilityKit';Method 2: Import multiple modules under a kit. For example:
import { UIAbility, Ability, Context } from '@kit.AbilityKit';Method 3: Import all modules under a kit. For example:
import * as module from '@kit.AbilityKit';
In the preceding information, module indicates the alias, which can be customized. The alias is used to call the APIs of the module.
If you use method 3, too many unnecessary modules may be imported. As a result, the compiled HAP file is too large and occupies too many resources. Therefore, exercise caution when using method 3..
Declarations in ArkTS allow you to introduce variables, constants, types, and functions.
Variable declaration
A declaration starting with the keyword let introduces a variable which can have different values during program execution.
let hi: string = 'hello'; hi = 'hello, world';
Constant declaration
A declaration starting with the keyword const introduces a read-only constant that can be assigned only once.
const hello: string = 'hello';
A compile-time error occurs if a new value is assigned to a constant.
Automatic type inference
You do not need to explicitly specify the type of a declared entity if a variable or a constant declaration contains an initial value, as all cases that allow the type to be inferred automatically are specified in the ArkTS Specification.
Both variable declarations are valid, and both variables are of the string type.
let hi1: string = 'hello'; let hi2 = 'hello, world';
Basic types and reference types
Basic types include number and string, which can accurately represent simple data types. You can directly store and access these types and compare their values.
Reference types include complex data structures such as objects, arrays, and functions. These types access data by reference. An object and array contain multiple values or KV pairs, and a function encapsulates executable code logic. The reference type accesses data in the memory through pointers. Changing the reference affects the original data.
Number type
ArkTS has the number type. Any integer and floating-point values can be assigned to a variable of this type.
Numeric literals include integer literals and floating-point literals with the decimal base.
Integer literals are classified into the following:
A floating-point literal includes the following:
Example:
let n1 = 3.14;
let n2 = 3.141592;
let n3 = 0.5;
let n4 = 1e2;
function factorial(n: number): number {
if (n <= 1) {
return 1;
}
return n * (n - 1);
}
// ...
factorial(n1) // 6.719600000000001
factorial(n2) // 6.728008294464001
factorial(n3) // 1
factorial(n4) // 9900The number type tends to lose precision when it represents very large integers (ranging from -9007199254740991 to 9007199254740991). You can use bigint to ensure the precision as required.
let bigInt: bigint = 999999999999999999999999999999999999999999999999999999999999n;
console.info('bigInt:' + bigInt.toString());Boolean type
The Boolean type represents logical values that are either true or false.
Usually variables of this type are used in conditional statements.
let isDone: boolean = false;
// ...
if (isDone) {
console.info('Done!');
}String type
A string is a sequence of characters; some characters can be set by using escape sequences.
A string literal consists of zero or more characters enclosed in single (') or double quotes ("). The special form of a string literal is the template literal enclosed in backticks (`).
let s1 = 'Hello, world!\n';
let s2 = 'this is a string';
let a = 'Success';
let s3 = `The result is ${a}`;Void type
The void type is used to specify that a function does not return a value.
This type has the only one value which is also void. As void is a reference type, it can be used as type argument for generic types.
class Class<T> {
// ...
}
let instance: Class<void>;Object type
An Object type is a base type for all reference types. Any value, including values of primitive types (they will be automatically boxed), can be directly assigned to variables of the type Object.
The Object type is used to represent types other than the primitive types.
let o1: Object = 'Alice'; let o2: Object = ['a', 'b']; let o3: Object = 1; let o4: object = [1, 2, 3];
Array type
An array is an object comprised of elements of data types assignable to the element type specified in the array declaration.
A value of an array is set by using array composite literal, which is a list of zero or more expressions enclosed in square brackets ([]). Each expression represents an element of the array. The length of the array is set by the number of expressions. Index of the first array element is 0.
The following example creates the array with three elements.
let names: string[] = ['Alice', 'Bob', 'Carol'];
Enum type
An enum type is a value type with a defined set of named values called enum constants.
In order to be used, an enum constant must be prefixed with an enum type name.
enum ColorSet { Red, Green, Blue }
let c: ColorSet = ColorSet.Red;A constant expression can be used to explicitly set the value of an enum constant.
enum ColorSet { White = 0xFF, Grey = 0x7F, Black = 0x00 }
let c: ColorSet = ColorSet.Black;Union type
A Union type is a reference type which is created as a combination of multiple types. Values of union types can be valid values of all types a union was created from.
class Cat {
public name: string = 'cat';
// ...
}
class Dog {
public name: string = 'dog';
// ...
}
class Frog {
public name: string = 'frog';
// ...
}
type Animal = Cat | Dog | Frog | number | string | null | undefined;
// Cat, Dog, and Frog are some types (classes or interfaces).
let animal: Animal = new Cat();
animal = new Frog();
animal = 42;
animal = 'dog';
animal = undefined;
// One may assign the variable of the union type with any valid value.There are different mechanisms to get a value of a particular type from a union.
Example:
class Cat { sleep () {}; meow () {} }
class Dog { sleep () {}; bark () {} }
class Frog { sleep () {}; leap () {} }
type Animal = Cat | Dog | Frog;
function foo(animal: Animal) {
if (animal instanceof Frog) { // Check whether animal is of the Frog type.
animal.leap(); // animal is of type Frog here.
}
animal.sleep(); // Any animal can sleep.
}Aliases type
Type Aliases provides names for anonymous types (such as array, function, object literal or union type) or alternative names for existing types.
// Two-dimensional array type.
type Matrix = number[][];
const gameBoard: Matrix = [
[1, 0],
[0, 1]
];
// Function type.
type Handler = (s: string, no: number) => string;
const repeatString: Handler = (str, times) => {
return str.repeat(times);
};
console.info(repeatString('abc', 3)); // 'abcabcabc'
// ...
// Generic function type.
type Predicate<T> = (x: T) => boolean;
const isEven: Predicate<number> = (num) => num % 2 === 0;
// Object type that can be null.
type NullableObject = Object | null;
class Cat {
}
let animalData: NullableObject = new Cat();
let emptyData: NullableObject = null;Assignment operators
Simple assignment operator = is used as in x = y.
Compound assignment operators combine an assignment with an operator. For example, a += b equals a = a + b,
where += is a compound assignment operator.
Compound assignment operators are as follows: +=, -=, ***=**, /=, %=, <<=**, **>>=, >>>=, &=, |=, and ^=.
Comparison operators
| Operator | Description |
|---|---|
| === | Returns true if both operands are strict equal. (Operands of different types are considered unequal.) |
| !== | Returns true if both operands are not strict equal. (Operands of different types are considered unequal.) |
| == | Returns true if both operands are equal. |
| != | Returns true if both operands are not equal. |
| > | Returns true if the left operand is greater than the right. |
| >= | Returns true if the left operand is greater than or equal to the right. |
| < | Returns true if the left operand is less than the right. |
| <= | Returns true if the left operand is less than or equal to the right. |
The differences between === and == are as follows.
// == compares only the values. console.info(String(null == undefined)); // true // === compares the values and types. console.info(String(null === undefined)); // false
Arithmetic operators
Unary operators are as follows: -, +, --, and ++.
Binary operators are as follows.
| Operator | Description |
|---|---|
| + | Addition |
| - | Subtraction |
| * | Multiplication |
| / | Division |
| % | Remainder after division |
Bitwise operators
| Operator | Description |
|---|---|
| a & b | Bitwise AND: sets each bit to 1 if the corresponding bits of both operands are 1, otherwise to 0. |
| a | b | Bitwise OR: sets each bit to 1 if at least one of the corresponding bits of both operands is 1, otherwise to 0. |
| a ^ b | Bitwise XOR: sets each bit to 1 if the corresponding bits of both operands are different, otherwise to 0. |
| ~ a | Bitwise NOT: inverts the bits of the operand. |
| a << b | Shift left: shifts the binary representation of a to the left by b bits. |
| a >> b | Arithmetic right shift: shifts the binary representation of a to the right by b bits with sign-extension. |
| a >>> b | Logical right shift: shifts the binary representation of a to the right by b bits with zero-extension. |
Logical operators
| Operator | Description |
|---|---|
| a && b | Logical AND |
| a || b | Logical OR |
| ! a | Logical NOT |
instanceof operator
The instanceof operator is used to check whether an object is an instance of a specified class or its child class at runtime.
Example:
obj instanceof className
The type of the return value is Boolean.
If obj is an instance of the className class or its child class, the return value is true. Otherwise, the return value is false.
Example:
class Person {}
const person = new Person();
if ((person instanceof Person)) {
console.info('true'); // true
}
class Animal {}
class Bird extends Animal {}
const bird = new Bird();
if (bird instanceof Animal) {
console.info('true'); // true
}if statement
An if statement is used to execute a sequence of statements when a logical condition is true, or another set of statements (if provided) otherwise.
The else part can also contain more if statements.
An if statement is as follows.
if (condition1) {
// Statement 1
} else if (condition2) {
// Statement 2
} else {
// The else statement
} All conditional expressions must be of any type. For types other than Boolean, implicit conversion rules are applied.
let s1 = 'Hello';
if (s1) {
console.info(s1); // Print "Hello"
}
let s2 = 'World';
if (s2.length != 0) {
console.info(s2); // Print "World"
}switch statement
A switch statement is used to execute a sequence of statements that match the value of a switch expression.
A switch statement is as follows.
switch (expression) {
case label1: // Execute if label1 is matched.
// ...
// Statement 1
// ...
break; // Can be omitted.
case label2:
case label3: // Execute if label2 or label3 is matched.
// ...
// Statement 23
// ...
break; // Can be omitted.
default:
// Default statement
}If the value of a switch expression equals the value of a certain label, then the corresponding statements are executed.
If there is no match, and the switch has the default clause, then the default statements are executed.
An optional break statement allows you to break out of the switch and continue executing the statement that follows the switch.
If there is no break, then the next statements in the switch are executed.
Conditional expressions
A conditional expression returns the result of one of the other two expressions based on the Boolean value of the first expression.
Example:
condition ? expression1 : expression2
If the value of condition is truthy (a value that is considered true), the expression1 is used as the result of the expression; otherwise, the expression2 is used.
Example:
let message = Math.random() > 0.5 ? 'Valid' : 'Failed';
If the value of condition is not a Boolean value, implicit conversion is performed.
Example:
console.info('a' ? 'true' : 'false'); // true
console.info('' ? 'true' : 'false'); // false
console.info(1 ? 'true' : 'false'); // true
console.info(0 ? 'true' : 'false'); // false
console.info(null ? 'true' : 'false'); // false
console.info(undefined ? 'true' : 'false'); // falsefor statement
The for statement is executed repeatedly until the specified loop exit condition result is false.
A for statement is as follows.
for ([init]; [condition]; [update]) {
statements
} When a for statement is executed, the following process takes place:
An init expression is executed, if any. This expression usually initializes one or more loop counters.
The condition is evaluated. If the value of condition is truthy (a value that is considered true), or if the conditional expression is omitted, the statements in the for body are to be executed. If the value of condition is falsy (a value that is considered false), the for loop terminates.
The statements of the for body are executed.
If there is an update expression, then the update expression is executed.
Go back to step 2.
Example:
let sum = 0;
for (let i = 0; i < 10; i += 2) {
sum += i;
}for-of statement
You can use the for-of statement to iterate over iterable types such as array, Set, Map, and string. Example:
for (forVar of IterableExpression) {
// process forVar
} Example:
for (let ch of 'a string object') {
console.info(ch);
// ...
}while statement
The while statement executes statements as long as the value of condition is true. Example:
while (condition) {
statements
} Example:
let n = 0;
let x = 0;
while (n < 3) {
n++;
x += n;
}do-while statement
If the value of condition is truthy (a value that is considered true), the statements is executed repeatedly. Example:
do {
statements
} while (condition) Example:
let i = 0;
do {
i += 1;
} while (i < 10)break statement
A break statement is used to terminate any loop statement or the switch statement.
Example:
let x = 0;
while (true) {
x++;
if (x > 5) {
break;
}
}A break statement with a label identifier transfers control out of the enclosing statement to the one which has the same label identifier.
Example:
let x = 1;
label: while (true) {
switch (x) {
case 1:
// statements
break label; // Break the while statement.
}
}continue statement
A continue statement stops the execution of the current loop iteration and passes control to the next iteration.
Example:
let sum = 0;
for (let x = 0; x < 100; x++) {
if (x % 2 == 0) {
continue;
}
sum += x;
}throw and try statements
A throw statement is used to throw an exception or an error:
throw new Error('this error')A try statement is used to catch and handle an exception or an error:
try {
// ...
} catch (e) {
// Handle the exception.
// ...
}The example below shows the throw and try statements used to handle the zero division case:
class ZeroDivisor extends Error {}
function divide (a: number, b: number): number {
if (b == 0) {
throw new ZeroDivisor();
}
return a / b;
}
function process(a: number, b: number) {
try {
let res = divide(a, b);
console.info('result: ' + res);
} catch (x) {
console.error('some error');
}
}finally clause is also supported:
function processData(s: string) {
let error: Error | null = null;
try {
console.info('Data processed: ' + s);
// ...
// Statement where an exception may occur
// ...
} catch (e) {
error = e as Error;
// ...
// Handle the exception.
// ...
} finally {
// Code that is executed regardless of whether an exception occurs
if (error != null) {
console.error(`Error caught: input='${s}', message='${error.message}'`);
}
}
}A function declaration introduces a named function, specifying its name, parameters, return type and body.
Below is a simple function with two string parameters and string return type:
Parameter type annotation: x: string, y: string explicitly specifies the parameter type as string.
Return value type: : string specifies the return value type of the function as string.
function add(x: string, y: string): string {
let z: string = `${x} ${y}`;
return z;
}For every parameter, its type annotation must be specified. An optional parameter allows you to omit this parameter when calling a function. The last parameter of a function can be a rest parameter.
The format of the optional parameter can be name?: Type.
function hello(name?: string) {
if (name == undefined) {
console.info('Hello!');
} else {
console.info(`Hello, ${name}!`);
}
}Another form contains an expression that specifies a default value. If the optional parameter is omitted in a function call, the default value of the parameter is used as the argument.
function multiply(n: number, coeff: number = 2): number {
return n * coeff;
}
// ...
multiply(2); // Return 2*2.
multiply(2, 3); // Return 2*3.The last parameter of a function can be a rest parameter in the format of ...restName: Type[]. It allows a function to receive a variable-length array for processing variable-quantity parameter inputs.
function sum(...numbers: number[]): number {
let res = 0;
for (let n of numbers) {
res += n;
}
return res;
}
// ...
sum(); // Return 0.
sum(1, 2, 3); // Return 6.If function return type can be inferred from its body content, then it can be omitted from the function declaration.
// Explicit return type
function foo(): string { return 'foo'; }
// Implicit return type inferred as string
function goo() { return 'goo'; }The return type of a function that does not need to return a value can be explicitly specified as void or omitted altogether. No return statement is needed for such functions.
Both notations below are valid:
function hi1() { console.info('hi'); }
function hi2(): void { console.info('hi'); }Variables and other entities defined in a function are local to the function and cannot be accessed from the outside.
If the name of a variable defined in the function is equal to the name of an entity in the outer scope, then the local definition shadows the outer entity.
let outerVar = 'I am outer ';
function func() {
let outerVar = 'I am inside';
console.info(outerVar); // Output: I am inside
}
// ...
func();Calling a function actually leads to the execution of its body, while the arguments of the call are assigned to the function parameters.
If the function is defined as follows:
function join(x: string, y: string): string {
let z: string = `${x} ${y}`;
return z;
}Then this function is called with two parameters of the type string:
let x = join('hello', 'world');
console.info(x); // Output: hello worldA Function type is commonly used as follows to define a callback:
type trigFunc = (x: number) => number // This is a function type.
function doAction(f: trigFunc) {
f(3.141592653589); // Call the function.
}
doAction(Math.sin); // Pass in the function as a parameter.A function can be defined as an arrow function. Example:
let sum = (x: number, y: number): number => {
return x + y;
}An arrow function return type can be omitted; in such case, it is inferred from the function body.
An expression can be specified as an arrow function to make the notation shorter, that is, the following two notations are equivalent:
let sum1 = (x: number, y: number) => { return x + y; }
let sum2 = (x: number, y: number) => x + yA closure is the combination of a function and the lexical environment within which that function was declared. This environment consists of any local variables that were in-scope at the time the closure was created.
In the following example, the f function returns a closure that captures the count variable. Each time z is called, the value of count is retained and incremented.
function f(): () => number {
let count = 0;
let g = (): number => { count++; return count; };
return g;
}
// ...
let z = f();
z(); // Return 1.
z(); // Return 2.A function can be specified to be called in different ways by writing overload signatures. To do so, several functions' headers that have the same name but different signatures are written to the same function and immediately followed by the single implementation function.
function foo1(x: number): void; /* First function definition */
function foo1(x: string): void; /* Second function definition */
function foo1(x: number | string): void { /* Implementation signature */
}
foo1(123); // OK. Use the first function definition.
foo1('aa'); // OK. Use the second function definition.An error occurs if two overload signatures have identical parameter lists.
A class declaration introduces a new type and defines its fields, methods and constructors.
In the following example, class Person is defined, which has fields name and surname, constructor, and the fullName method:
class Person {
public name: string = '';
public surname: string = '';
constructor (n: string, sn: string) {
this.name = n;
this.surname = sn;
}
fullName(): string {
return this.name + ' ' + this.surname;
}
}After the class is defined, its instances can be created by using the keyword new:
let p = new Person('John', 'Smith');
console.info(p.fullName());Alternatively, an instance can be created by using object literals:
class Point {
public x: number = 0;
public y: number = 0;
}
let p: Point = {x: 42, y: 42};A field is a variable of some type that is declared directly in a class.
Classes may have instance fields or static fields.
Instance fields
Instance fields exist on every instance of a class. Each instance has its own set of instance fields.
An instance of the class is used to access an instance field.
class Person1 {
public name: string = '';
public age: number = 0;
constructor(n: string, a: number) {
this.name = n;
this.age = a;
}
getName(): string {
return this.name;
}
}
// ...
let p1 = new Person1('Alice', 25);
p1.name; // Alice
let p2 = new Person1('Bob', 28);
p2.getName(); // BobStatic fields
The keyword static is used to declare a field as static. Static fields belong to the class itself, and all instances of the class share one static field.
The class name is used to access a static field:
class Person2 {
public static numberOfPersons = 0;
constructor() {
// ...
Person2.numberOfPersons++;
// ...
}
}
Person2.numberOfPersons;Field initializers
ArkTS requires that all fields be explicitly initialized either when the field is declared or in the constructor. This is similar to the strictPropertyInitialization mode of the standard TypeScript. Such behavior is enforced to minimize unexpected runtime errors and improve performance.
The following code (invalid in ArkTS) is error-prone:
class Person {
name: string; // undefined
setName(n: string): void {
this.name = n;
}
getName(): string {
// Return type "string" hides the fact that name can be "undefined".
// A more appropriate approach is to annotate the return type as "string | undefined" to inform users of all possible return values of this API.
return this.name;
}
}
let jack = new Person();
// Assume that no value is assigned to name, that is, "jack.setName('Jack')" is not called.
jack.getName().length; // Runtime exception: name is undefined. In ArkTS, you should write code as follows.
class Person3 {
public name: string = '';
setName(n: string): void {
this.name = n;
}
// The type is 'string' in all cases; "null" and "undefined" are impossible.
getName(): string {
return this.name;
}
}
let jack = new Person3();
// Assume that no value is assigned to name, that is, "jack.setName('Jack')" is not called.
jack.getName().length; // 0, no runtime error.The following shows how our code behaves if the field name can be undefined:
class Person {
name?: string; // The field may be undefined.
setName(n: string): void {
this.name = n;
}
// Compile-time error: name can be "undefined". Therefore, the return value type of the API cannot be defined as string only.
getNameWrong(): string {
return this.name;
}
getName(): string | undefined { // Return type matches the type of name.
return this.name;
}
}
let jack = new Person();
// Assume that no value is assigned to name, that is, "jack.setName('Jack')" is not called.
// Compile-time error: Compiler suspects that the next line of code possibly accesses something undefined.
jack.getName().length; // Compilation failed.
jack.getName()?.length; // Successful compilation and no runtime error. getter and setter
setter and getter can be used to provide controlled access to class properties.
In the following example, a setter is used to forbid setting invalid values of the _age property:
class Person4 {
public name: string = '';
private _age: number = 0;
get age(): number { return this._age; }
set age(x: number) {
if (x < 0) {
throw Error('Invalid age argument');
}
this._age = x;
}
}
// ...
let p = new Person4();
p.age; // Output 0.
p.age = -42; // Error will be thrown as an attempt to set incorrect age.A class can define a getter or a setter.
A method is a function that belongs to a class. A class can define instance methods, static methods or both. A static method belongs to the class itself, and can have access to static fields only. A while instance method has access to both static (class) fields and instance fields including private ones of its class.
Instance method
The example below illustrates how instance methods work.
The calculateArea method calculates the area of a rectangle by multiplying the height by the width:
class RectangleSize {
private height: number = 0;
private width: number = 0;
constructor(height: number, width: number) {
this.height = height;
this.width = width;
}
calculateArea(): number {
return this.height * this.width;
}
}To use an instance method, it must be called on an instance of the class:
let square = new RectangleSize(10, 10); square.calculateArea(); // Output: 100
Static method
The keyword static is used to declare a method as static. A static method belongs to the class itself and have access to static fields only.
A static method defines a common behavior of the class as a whole.
The class name is used to call a static method.
class C2 {
public static staticMethod(): string {
return 'this is a static method.';
}
}
// ...
console.info(C2.staticMethod());Inheritance
A class can extend another class (called base class) and implement multiple APIs using the following syntax:
class [extends BaseClassName] [implements listOfInterfaces] {
// ...
} An inheritance class extends the fields and methods of the base class, but does not extend the constructor. An inheritance class allows you to define new fields and methods or override the methods defined by the base class.
The class that is being extended by another class is called 'base class', 'parent class', or 'superclass'. The class that extends another class is called 'extended class', 'derived class', or 'child class'.
Example:
class Person5 {
public name: string = '';
public _age = 0;
get age(): number {
return this._age;
}
}
class Employee extends Person5 {
public salary: number = 0;
calculateTaxes(): number {
return this.salary * 0.42;
}
}A class containing the implements clause must implement all methods defined in all listed APIs, except the methods defined with default implementation.
interface DateInterface {
now(): string;
}
class MyDate implements DateInterface {
now(): string {
// Here is the implementation.
return 'now';
}
}Access to super
The super keyword can be used to access the methods and constructors of the super class. It is often used to extend basic functionality of child class with the required behavior taken from the super class:
class RectangleSize {
protected height: number = 0;
protected width: number = 0;
constructor (h: number, w: number) {
this.height = h;
this.width = w;
}
draw() {
/* Draw bounds. */
}
}
class FilledRectangle extends RectangleSize {
public color = ''
constructor (h: number, w: number, c: string) {
super(h, w); // Call of the super constructor.
this.color = c;
}
draw() {
super.draw(); // Call of the super method.
/* Fill the rectangle. */
}
}Method overriding
A child class can override implementation of a method defined in its superclass. An overridden method must have the same types of parameters, and same or derived return type as the original method.
class RectangleSize {
// ...
area(): number {
// Implementation
return 0;
}
}
class Square extends RectangleSize {
private side: number = 0;
area(): number {
return this.side * this.side;
}
}Method overload signature
A method can be specified to be called in different ways by writing overload signatures. To do so, several methods' headers that have the same name but different signatures are written to the same method and immediately followed by the single implementation method.
class C {
foo(x: number): void; /* First signature */
foo(x: string): void; /* Second signature */
foo(x: number | string): void { /* Implementation signature */
}
}
let c = new C();
c.foo(123); // OK. Use the first signature.
c.foo('aa'); // OK. Use the second signature.An error occurs if two overload signatures have identical names and parameter lists.
A class declaration may contain a constructor that is used to initialize object state.
A constructor is defined as follows.
constructor ([parameters]) {
// ...
} If no constructor is defined, then a default constructor with an empty parameter list is created automatically. Example:
class Point {
public x: number = 0;
public y: number = 0;
}
let p = new Point();In this case, the default constructor fills the instance fields with default values for the field types.
Constructor in a derived class
The first statement of a constructor body can use the keyword super to explicitly call a constructor of the direct superclass.
class RectangleSize {
constructor(width: number, height: number) {
// ...
}
}
class Square extends RectangleSize {
constructor(side: number) {
super(side, side);
}
}Constructor overload signature
A constructor can be specified to be called in different ways by writing overload signatures. To do so, several constructors' headers that have the same name but different signatures are written to the same constructor and immediately followed by the single implementation constructor.
class C {
constructor(x: number) /* First signature */
constructor(x: string) /* Second signature */
constructor(x: number | string) { /* Implementation signature */
}
}
let c1 = new C(123); // OK. Use the first signature.
let c2 = new C('abc'); // OK. Use the second signature.An error occurs if two overload signatures have identical names and parameter lists.
Both methods and properties of a class can have visibility modifiers.
Visibility modifiers include private, protected, and public. The default visibility is public.
Public
The public members (fields, methods, constructors) of a class are visible in any part of the program, where their class is visible.
Private
A private member cannot be accessed outside the class where it is declared in. Example:
class C {
public x: string = '';
private y: string = '';
set_y (new_y: string) {
this.y = new_y; // OK, as y is accessible within the class itself.
}
}
let c = new C();
c.x = 'a'; // OK, as the field is public.
c.y = 'b'; // Compile-time error: 'y' is invisible. Protected
The modifier protected acts much like the modifier private, but the protected members are also accessible in derived classes. Example:
class Base {
protected x: string = '';
private y: string = '';
}
class Derived extends Base {
foo() {
this.x = 'a'; // OK. Access the protected member.
this.y = 'b'; // Compile-time error. 'y' is invisible because it is private.
}
} An object literal is an expression that can be used to create a class instance and provide some initial values. It can be used instead of the expression new as it is more convenient in some cases.
An object literal is a list of 'property name: value' enclosed in a pair of curly brackets ({}).
class C {
public n: number = 0;
public s: string = '';
}
let c: C = {n: 42, s: 'foo'};ArkTS is a statically typed language. As shown in the preceding example, object literals can be used only in contexts where the literal type can be inferred. Other valid cases are illustrated below:
class C1 {
public n: number = 0;
public s: string = '';
}
function foo(c1: C1) {}
let c1: C1
c1 = {n: 42, s: 'foo'}; // Use the variable type.
foo({n: 42, s: 'foo'}); // Use the parameter type.
function bar(): C1 {
return {n: 42, s: 'foo'}; // Use the return type.
}You can also use the object literals in array element types or class field types.
class C {
public n: number = 0;
public s: string = '';
}
let cc: C[] = [{n: 1, s: 'a'}, {n: 2, s: 'b'}];Object literal of Record type
The generic Record<K, V> type is used to map the properties of a type (Key type) to another type (Value type). A special form of object literal is used to initialize the value of such type:
let map: Record<string, number> = {
'John': 25,
'Mary': 21
};
// ...
map['John']; // 25The K type can be either string or number (excluding bigint), while V can be any type.
interface PersonInfo {
age: number;
salary: number;
}
let map: Record<string, PersonInfo> = {
'John': { age: 25, salary: 10},
'Mary': { age: 21, salary: 20}
}A class with the modifier abstract is known as abstract class. Abstract classes can be used to represent notions that are common to some set of more concrete notions.
A compile-time error occurs if an attempt is made to create an instance of an abstract class:
abstract class X {
field: number;
constructor(p: number) {
this.field = p;
}
}
let x = new X(666) // Compile-time error: Cannot create an instance of an abstract class. Child classes of an abstract class can be non-abstract or in turn abstract. A non-abstract child class of an abstract parent class can be instantiated. As a result, a constructor for the abstract class, and field initializers for non-static fields of that class are executed:
abstract class Base {
private field: number;
constructor(p: number) {
this.field = p;
}
}
class Derived extends Base {
constructor(p: number) {
super(p);
}
}
let x = new Derived(666);Abstract method
A method with the modifier abstract is considered an abstract method. Abstract methods do not have bodies, that is, they can be declared but not implemented.
Only abstract classes can have abstract methods. A compile-time error occurs if a non-abstract class has an abstract method:
class Y {
abstract method(p: string) // Compile-time error: Abstract methods can only appear within an abstract class.
} An interface declaration introduces a new type. Interfaces are a common way of defining contracts between various part of codes.
Interfaces are used to write polymorphic code, which can be applied to any class instances that implement a particular interface.
An interface usually contains properties and method declarations.
Example:
interface Style {
color: string; // Property
}
interface AreaSize {
calculateAreaSize(): number; // Method declaration
someMethod(): void; // Method declaration
}Example of a class implementing an interface:
// Interface:
interface AreaSize {
calculateAreaSize(): number; // Method declaration
someMethod(): void; // Method declaration
}
// Implementation
class RectangleSize implements AreaSize {
private width: number = 0;
private height: number = 0;
someMethod(): void {
console.info('someMethod called');
}
calculateAreaSize(): number {
this.someMethod(); // Call another method and return the result.
return this.width * this.height;
}
}An interface property can be in a form of field, getter, setter, or both getter and setter.
A property field is just a shortcut notation of a getter or setter pair, and the following notations are equal:
interface Style {
color: string;
}interface Style {
get color(): string;
set color(x: string);
}A class that implements an interface may also use a short or a long notation:
interface Style {
color: string;
}
class StyledRectangle implements Style {
public color: string = '';
}interface Style {
color: string;
}
class StyledRectangle implements Style {
private _color: string = '';
get color(): string { return this._color; }
set color(x: string) { this._color = x; }
}An interface may extend other interfaces, as shown in the example below:
interface Style {
color: string;
}
interface ExtendedStyle extends Style {
width: number;
}An extended interface contains all properties and methods of the interface it extends, and can also add its own properties and methods.
Abstract classes and interfaces cannot be instantiated. Abstract classes are abstractions of classes, used to capture the general characteristics of child classes, and interfaces are abstractions of behavior. The differences between abstract classes and interfaces in ArkTS are as follows.
// The Bird class extends the abstract class Animal and implements multiple interfaces CanFly and CanSwim.
class Bird extends Animal implements CanFly, CanSwim {
// ...
} interface MyInterface {
// Error: The interface cannot contain static members.
static staticMethod(): void;
// Error: The interface cannot contain static code blocks.
static { console.info('static'); };
}
abstract class MyAbstractClass {
// Correct: An abstract class can contain static methods.
static staticMethod(): void { console.info('static'); }
// Correct: An abstract class can contain static code blocks.
static { console.info('static initialization block'); }
} abstract class MyAbstractClass {
// Correct: An abstract class can contain implementation of methods.
func(): void { console.info('func'); }
}
interface MyInterface {
// Error: Interfaces are completely abstract and there is no implementation of methods.
func(): void { console.info('func'); }
} abstract class MyAbstractClass {
constructor(){} // Correct: Abstract classes can have constructors.
}
interface MyInterface {
constructor(); // Error: Interfaces cannot contain constructors.
} Generic type and function enable code to operate on multiple data types in a type-safe way without writing repetitive logic for each type.
A class and an interface can be defined as generics. Add parameters to the type definition, such as the type parameter Element in the following example:
class CustomStack<Element> {
public push(e: Element):void {
// ...
}
}To use type CustomStack, the type argument must be specified for each type parameter:
let s = new CustomStack<string>();
s.push('hello');Compiler ensures type safety while working with generic types and functions. Refer to the example below:
let s = new CustomStack<string>(); s.push(55); // A compile-time error is thrown.
Type parameters of generic types can be bounded. For example, the Key type parameter in the MyHashMap<Key, Value> class must have the hash method.
interface Hashable {
hash(): number;
}
class MyHashMap<Key extends Hashable, Value> {
public set(k: Key, v: Value) {
let h = k.hash();
// Other code is omitted here.
}
}In the above example, the Key type extends Hashable, and all methods of Hashable interface can be called for keys.
Use a generic function to create a more universal code. Consider a function that returns the last element of the array:
function last(x: number[]): number {
return x[x.length - 1];
}
// ...
last([1, 2, 3]); // 3If the same function needs to be defined for any array, then define it as a generic with a type parameter:
function last1<T>(x: T[]): T {
return x[x.length - 1];
}Now, the function can be used with any array.
In a function call, type argument can be set explicitly or implicitly:
// Explicit type argument let res1: string = last<string>(['aa', 'bb']); let res2: number = last<number>([1, 2, 3]); // Implicit type argument // Compiler determines the type argument based on the type of the call arguments. let res3: number = last([1, 2, 3]);
Type parameters of generic types can have defaults. It allows using just the generic type name instead of specifying the actual type arguments. The example below illustrates this feature of both class and function.
class SomeType {}
interface Interface <T1 = SomeType> { }
class Base <T2 = SomeType> { }
class Derived1 extends Base implements Interface { }
// Derived1 is semantically equivalent to Derived2.
class Derived2 extends Base<SomeType> implements Interface<SomeType> { }
function foo<T = number>(): void {
// ...
}
foo();
// Such function is semantically equivalent to the call below:
foo<number>();All types in ArkTS by default are non-nullable, so the value of a type cannot be null. It is similar to TypeScript behavior in strict null checking mode (strictNullChecks), but the rules in ArkTS are stricter.
In the following example, all lines lead to a compile-time error:
let x: number = null; // Compile-time error let y: string = null; // Compile-time error let z: number[] = null; // Compile-time error
A variable that can have a null value is defined with a union type T | null.
let x: number | null = null;
x = 1; // ok
x = null; // ok
if (x != null) { /* do something */ }A postfix operator ! can be used to assert that its operand is non-null.
When applied to a value of a nullable type, the type becomes non-nullable at compile time. For example, the type of the value is changed from T | null to T:
class A {
value: number = 0;
}
function foo(a: A | null) {
a.value; // Compile-time error: cannot access to a nullable value.
a!.value; // If the value of a is not null at runtime, the fields of a can be accessed; if the value of a is null at runtime, a runtime exception occurs.
} The null-coalescing binary operator ?? checks whether the evaluation of the left-hand-side expression is equal to null or undefined. If it is, the result of the expression is the right-hand-side expression; otherwise, it is the left-hand-side expression.
In other words, a ?? b equals the ternary operator (a != null && a != undefined) ? a : b.
In the following example, the getNick method returns a nickname if it is set; otherwise, an empty string is returned:
class Person {
// ...
public nick: string | null = null;
getNick(): string {
return this.nick ?? '';
}
}If the object property is undefined or null when it is being accessed, the optional chaining operator returns undefined.
class Person {
public nick: string | null = null;
public spouse?: Person
setSpouse(spouse: Person): void {
this.spouse = spouse;
}
getSpouseNick(): string | null | undefined {
return this.spouse?.nick;
}
constructor(nick: string) {
this.nick = nick;
this.spouse = undefined;
}
}The return type of getSpouseNick must be string | null | undefined, as the method can return null or undefined.
The optional chaining can be of any length and can contain any number of ?. operators.
In the following example, if the spouse property of the Person instance and the nick property of spouse both are not null, the spouse.nick is output. Otherwise, undefined is output.
class Person {
public nick: string | null = null;
public spouse?: Person;
constructor(nick: string) {
this.nick = nick;
this.spouse = undefined;
}
}
let p: Person = new Person('Alice');
p.spouse?.nick; // undefinedPrograms are organized as sets of compilation units or modules.
Each module creates its own scope, that is, any declarations (variables, functions, classes, etc.) declared in the module are not visible outside that module unless they are explicitly exported.
Conversely, a variable, function, class, or interface exported from another module must first be imported to a module.
A top-level declaration can be exported by using the keyword export.
A declared name that is not exported is considered private and can be used only in the module where it is declared.
export class Point {
public x: number = 0;
public y: number = 0;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
export let origin = new Point(0, 0);
export function Distance(p1: Point, p2: Point): number {
return Math.sqrt((p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y));
}Exporting default objects
class Demo{
constructor(){
}
}
export default new Demo();Static import
Import declarations are used to import entities exported from other modules and provide their bindings in the current module. An import declaration consists of two parts:
Import bindings may have several forms.
Let's assume a module has the path ./utils and export entities X and Y.
An import binding of the form *** as A** binds the name A, and all entities exported from the module defined by the import path can be accessed by using the qualified name A.name:
import * as Utils from './utils'; // ... Utils.X // Denote X from Utils. Utils.Y // Denote Y from Utils.
An import binding of the form { ident1, ..., identN } binds an exported entity with a specified name, which can be used as a simple name:
import { X, Y } from './utils';
// ...
X // Denote X from utils.
Y // Denote Y from utils.If a list of identifiers contains an alias in the form ident as alias, the entity ident is bound under the name alias:
import { X as Z, Y } from './utils';
Z // Denote X from Utils.
Y // Denote Y from Utils.
X // Compile-time error: 'X' is invisible. Dynamic import
Unlike static import, dynamic import allows you to load a module conditionally or on demand.
The import() syntax, commonly called dynamic import, is a function-like expression that allows for dynamic loading of a module. A promise is returned when this method is invoked.
In the following example, import(modulePath) loads a module and returns a promise that resolves into a module object that contains all its exports. This expression can be called from any place in the code.
// Calc.ets
export function add(a: number, b: number): number {
let c = a + b;
console.info('Dynamic import, %d + %d = %d', a, b, c);
return c;
}// Index.ets
// ESObject is a type used to annotate JS/TS objects in ArkTS cross-language call scenarios.
import('./Calc').then((obj: ESObject) => {
console.info(obj.add(3, 5));
}).catch((err: Error) => {
console.error('Module dynamic import error: ', err);
});You can also use let module = await import(modulePath) inside an async function.
// Say.ets
export function hi() {
console.info('Hello');
}
export function bye() {
console.info('Bye');
}Then dynamic import can be like this:
async function test() {
let ns = await import('./say');
let hi = ns.hi;
let bye = ns.bye;
hi();
bye();
}For more details about dynamic import, see Dynamic Import.
Top-level statements are those written at the outermost layer of a module, not wrapped by any function, class, or block scope. These statements include variable declarations, function declarations, and expressions.
The keyword this can only be used in instance methods of a class.
Example
class A {
private count: string = 'a';
m(i: string): void {
this.count = i;
}
}Constraints:
Example
class A {
n: number = 0;
f1(arg1: this) {} // Compile-time error: Type notation using `this` is not supported.
static f2(arg1: number) {
this.n = arg1; // Compile-time error: `this` is not allowed in the static methods of classes.
}
}
function foo(arg1: number) {
this.n = i; // Compile-time error: `this` is not allowed in functions.
} The keyword this used as a primary expression denotes a value that is a reference to the following:
Annotation is a language feature that changes the semantics of application declarations by adding metadata.
The declaration and usage of annotations are as follows.
Example
// Declaration of an annotation:
@interface ClassAuthor {
authorName: string
}
// Usage of an annotation:
@ClassAuthor({authorName: "Bob"})
class MyClass {
// ...
}The name of the annotation to be used must be prefixed with the @ symbol (for example, @MyAnno). No space or line separator is allowed between the @ symbol and the name.
ClassAuthor({authorName: "Bob"}) // Compile-time error: The annotation must be prefixed with the @ symbol.
@ ClassAuthor({authorName: "Bob"}) // Compile-time error: No space or line separator is allowed between the @ symbol and the name. If the annotation name cannot be accessed, a compile-time error occurs.
Annotation declarations can be exported and used in other files.
Multiple annotations can be applied to the same declaration (the sequence of annotations does not affect the usage).
@MyAnno()
@ClassAuthor({authorName: "John Smith"})
class MyClass1 {
// ...
}Annotations are not features in TypeScript and can be used only in .ets and .d.ets files.
Notice
When you build a source code HAR in release mode and enable obfuscation, the build products, which are JS files, will be removed during build due to the lack of an annotation implementation mechanism in JS. As a result, AOP code instrumentation cannot be implemented through the annotation.
To avoid function exceptions caused by this issue, do not use annotations in JS HAR (which exists in the build products).
If you need to build a HAR that contains annotations in release mode with obfuscation enabled, build a bytecode HAR.
Custom annotations are supported since API version 20.
Declaration of custom annotation
The definition of custom annotation is similar to that of interface. The interface keyword is prefixed with the @ symbol.
The following types can be used for annotation fields:
The default value of an annotation field must be specified using a constant expression.
The following types of constant expressions are used:
If the enums cannot be determined during compilation, a compile-time error is reported.
// a.ts
export enum X {
x = foo(); // x is not a constant that can be determined during compilation.
}
// b.ets
import {X} from './a';
@interface Position {
data: number = X.x; // Compile-time error: The default value of the annotation field must be a constant expression.
} Annotations must be defined in the top-level scope. Otherwise, a compile-time error is reported.
The name of an annotation cannot be the same as that of another entity visible in the scope where the annotation is defined. Otherwise, a compile-time error is reported.
Annotations do not support the merging feature in TypeScript. Otherwise, a compile-time error is reported.
namespace ns {
@interface MetaInfo { // Compile-time error: Annotations must be defined in the top-level scope.
// ...
}
}
@interface Position {
// ...
}
class Position { // Compile-time error: The name of an annotation cannot be the same as that of another entity visible in the scope where the annotation is defined.
// ...
}
@interface ClassAuthor {
name: string;
}
@interface ClassAuthor { // Compile-time error: The name of an annotation cannot be the same as that of another entity visible in the scope where the annotation is defined.
data: string;
} Annotations are not types. If annotations are used as types, a compile-time error is reported. For example, type aliases are used for annotations.
@interface Position {}
type Pos = Position; // Compile-time error: Annotations are not types. Annotations cannot be added to the getter and setter methods of a class. Otherwise, a compile-time error is reported.
@interface ClassAuthor {
authorName: string;
}
@ClassAuthor({authorName: 'John Smith'})
class MyClass {
private _name: string = 'Bob';
@ClassAuthor({authorName: 'John Smith'}) // Compile-time error: Annotations cannot be added to the getter and setter methods of a class.
get name() {
return this._name;
}
@ClassAuthor({authorName: 'John Smith'}) // Compile-time error: Annotations cannot be added to the getter and setter methods of a class.
set name(authorName: string) {
this._name = authorName;
}
} Usage of custom annotation
The following is an example of annotation declaration:
@interface ClassPreamble {
authorName: string;
revision: number = 1;
}
@interface MyAnno {}Currently, annotations can be used only for class declarations and method declarations. The same annotation can be used for classes and methods.
The following is an example of annotation usage:
@ClassPreamble({authorName: "John", revision: 2})
class C1 {
// ...
}
@ClassPreamble({authorName: "Bob"}) // The default value of revision is 1.
class C2 {
// ...
}
@MyAnno() // The same annotation can be used for classes and methods.
class C3 {
@MyAnno()
foo() {}
@MyAnno()
static bar() {}
}The field sequence in an annotation does not affect the usage.
@ClassPreamble1({authorName: "John", revision: 2})
// ...
// the same as:
@ClassPreamble1({revision: 2, authorName: "John"})When using an annotation, you must assign values to the fields that do not have default values. Otherwise, a compile-time error occurs.
The value to assign must be of the same type as the annotation declaration and only constant expressions can be used.
@ClassPreamble() // Compile-time error: The authorName field is not defined.
class C1 {
// ...
} If a field of the array type is defined in an annotation, use an array literal to set the value of the field.
@interface ClassPreamble2 {
authorName: string;
revision: number = 1;
reviewers: string[];
}
@ClassPreamble2(
{
authorName: "Alice",
reviewers: ["Bob", "Clara"]
}
)
class C0 {
// ...
}If it is not necessary to define an annotation field, you can omit the parentheses following the annotation name.
@MyAnno
class C4 {
// ...
}Importing and exporting annotations
Annotations can also be imported and exported. Currently, only the annotations in the export @interface form can be exported.
Example
export @interface MyAnno1 {}Currently, only the annotations in the import {} and import * as forms can be imported.
Example
// MyAnno.ets
export @interface MyAnno2 {}
export @interface ClassAuthor2 {}// Annotation.ets
import { MyAnno2 } from './MyAnno';
import * as ns from './MyAnno';
// ...
@MyAnno2
@ns.ClassAuthor2
class C {
// ...
}import { MyAnno as Anno } from './a'; // Compile-time error: Annotation renaming is not allowed in import. Any other forms of import or export are not allowed for annotations. Otherwise, a compile-time error is reported.
import type { MyAnno } from './a'; // Compile-time error: The type symbol is not allowed for annotation import and export. // MyAnno.ets
export @interface Anno {}
export @interface ClassAuthor1 {}
console.info('hello'); // Annotation.ets
import { MyAnno2 } from './MyAnno';
import * as ns from './MyAnno';
// Only the Anno module is imported, which does not execute console.info of MyAnno.ets.
class X {
// ...
}Annotations in .d.ets files
Annotations can be used in .d.ets files.
You can declare annotations using ambient declarations in .d.ets files.
ambientAnnotationDeclaration: 'declare' userDefinedAnnotationDeclaration ;
Example
// NameAnno.d.ets
export declare @interface ClassAuthor3 {}In the preceding declaration:
// NameAnno.d.ets
export declare @interface NameAnno{name: string = ""}// MyAnno.ets
export @interface NameAnno{name: string = ""} // okThe ambient declaration of an annotation is similar to that of a class and can also be imported.
// NameAnno.d.ets
export declare @interface MyAnno {}// ImportMyAnno.ets
import { MyAnno } from './NameAnno';
@MyAnno
class C {
// ...
}.d.ets files automatically generated by a compiler
When a compiler automatically generates .d.ets files based on ETS code, the following situations may occur:
When the annotation definition is exported, the annotation definition in the source code is retained in the .d.ets file.
// MyAnno.ets
export @interface ClassAuthor5 {}
@interface MethodAnno { // Not exported
data: number;
}// NameAnno.d.ets
export declare @interface ClassAuthor3 {}If all the following conditions are met, the annotation instance of the entity in the source code is retained in the .d.ets file.
2.1 The annotation definition (including imported annotation) is exported.
2.2 If the entity is a class, the class is exported.
2.3 If the entity is a method, the class is exported, and the method is not private.
// MyAnno.ets
import { ClassAuthor4 } from './Author';
export @interface MethodAnno4 {
data: number = 0;
}
@ClassAuthor4
class MyClass {
@MethodAnno4({data: 123})
foo() {}
@MethodAnno4({data: 456})
private bar() {}
}// Declaration file generated by the NameAnno.d.ets compiler
import { ClassAuthor4 } from './Author';
export declare @interface MethodAnno4 {
data: number = 0;
}
@ClassAuthor4
export declare class MyClass {
@MethodAnno4({data: 123})
foo(): void;
bar; // Annotations are not retained for private methods.
}.d.ets file generated by the developer
The annotation information in the .d.ets file generated by you is not automatically applied to the implemented source code.
Example
// Declaration file generated by you in NameAnno.d.ets
@interface ClassAuthor6 {}
@ClassAuthor6 // The declaration file contains annotations.
class C {
// ...
}// Source code of the declaration file implemented by you in MyAnno.ets
@interface ClassAuthor6 {}
// The implementation file does not contain annotations.
class C {
// ...
}In the final build product, class C does not have annotations.
Duplicate annotations and inheritance
The same annotation cannot be used repeatedly for the same entity. Otherwise, a compile-time error occurs.
@MyAnno({name: "123", value: 456})
@MyAnno({name: "321", value: 654}) // Compile-time error: Duplicate annotations are not allowed.
class C {
// ...
} A child class does not extend the annotations of the base class or that of the methods of the base class.
Annotations, abstract classes, and abstract methods
Annotations cannot be used for abstract classes or abstract methods. Otherwise, a compile-time error occurs.
@MyAnno // Compile-time error: Annotations cannot be used for abstract classes and abstract methods.
abstract class C {
@MyAnno
abstract foo(): void; // Compile-time error: Annotations cannot be used for abstract classes and abstract methods.
} This section demonstrates the mechanism provided by ArkTS for creating GUIs. ArkUI provides a series of extended capabilities based on TypeScript to declaratively describe the GUIs of an application and the interaction between GUI components.
MVVM sample code provides a complete ArkUI-based application to demonstrate its GUI programming features.
For more details about ArkUI features, see Basic Syntax Overview.