**
1. What are Decorators in HarmonyOS?
**
In ArkTS, Decorators are special declarations that annotate and modify classes, methods, properties, etc.
As an extended programming language based on TypeScript, ArkTS inherits TypeScript's decorator support. They are tools for metaprogramming, enabling developers to add extra functionality without altering original code structures. For example, in HarmonyOS development, decorators define component properties, lifecycle methods, etc. The @Component decorator marks a class as a HarmonyOS component class.
Key decorators in HarmonyOS include:
State Decorators (V1 & V2): State, Prop, Link, ObservedV2, etc.
Component Decorators: Entry、 CustomDialog、 Component, Builder, etc.
**
2. Basic Principles of Decorators
**
ArkTS implements decorators as functions that extend code non-invasively. They fall into three main types:
Class Decorators
Declared above classes, they take the class constructor as an argument and can:
Modify the class constructor
Add properties and methods to the class
Attach metadata annotations to the class
function logDecorator(constructor: Function) {
console.log(`Class ${constructor.name} is created.`);
}
@logDecorator
class MyComponent {
constructor() {
console.log('MyComponent instance is created.');
}
}
const myComponent = new MyComponent();
Method Decorators are applied to class methods and execute when the method is defined. They accept three parameters: the target object, the method name, and the method descriptor. Method decorators can modify method behavior, such as adding logging, performing permission checks, or implementing throttling/debouncing. In the OpenHarmony open-source system, referring to the system camera source code, a custom method decorator is implemented as follows. However, in HarmonyOS, ArkTS fails strict type checking for any, rendering this syntax incompatible.
/*
* Copyright (c) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Log } from '../../utils/Log';
const TAG = '[Decorators]:'
export function debounce(timeout: number) {
return function inner(target: any, propKey: string, descriptor: PropertyDescriptor) {
let curFunc: number = 0;
const original = descriptor.value;
descriptor.value = function (...args: string[]) {
Log.log(`${TAG} debounce invoke ${propKey} curFunc: ${curFunc}`);
curFunc && clearTimeout(curFunc);
curFunc = setTimeout(() => original.call(this, ...args), timeout);
};
};
}
export function throttle(waitTime: number) {
return function inner(target: any, propKey: string, descriptor: PropertyDescriptor) {
let lastTime: number = 0;
const original = descriptor.value;
descriptor.value = function (...args: string[]) {
let curTime = Date.now();
Log.log(`${TAG} throttle invoke ${propKey} timeInterval: ${curTime - lastTime}`);
if (curTime - lastTime >= waitTime) {
original.call(this, ...args);
lastTime = curTime;
}
};
};
}
Property Decorators are applied to class properties and execute when the property is defined. They accept two parameters: the target object and the property name. Property decorators can modify property accessors, such as adding validation logic or implementing caching. However, in HarmonyOS, ArkTS fails strict type checking for any, rendering this syntax incompatible.
function positiveNumber(target: any, propertyKey: string) {
let value: number;
const getter = function () {
return value;
};
const setter = function (newValue: number) {
if (newValue < 0) {
throw new Error(`${propertyKey} must be a positive number.`);
}
value = newValue;
};
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
}
class MyModel {
@positiveNumber
age: number = 20;
}
const myModel = new MyModel();
myModel.age = -1;
3. How to Define Custom Decorators in HarmonyOS
As discussed, HarmonyOS enforces strict ArkTS syntax rules where any and unknown types are disallowed. Therefore, we must use Object to replace the any type for the target parameter.
The value property in PropertyDescriptor is also of type any, which works in standard TypeScript but requires a workaround in ArkTS. We achieve this by accessing the value property via the Function type, then assigning target: Object, key: string, and descriptor: PropertyDescriptor using the rest parameters syntax (...args).
...args is the rest parameters syntax in JavaScript and TypeScript (upon which ArkTS is based).
To define a class decorator in HarmonyOS, follow this pattern. The same principle applies to custom property decorators:
function methodLogger(target: Object, key: string, descriptor: PropertyDescriptor) {
const originalMethod: Function = descriptor.value;
descriptor.value = (...args: Object[]) => {
console.log(`Calling ${target.constructor.name} method ${key} with argument: ${args}`)
const result: Object = originalMethod(...args)
console.log(`Method ${key} returned: ${result}`)
return result
}
return descriptor;
}
@Entry
@Component
struct DecoratorDemoComponent {
@State message: string = 'Hello HarmonyOS';
@methodLogger
private processData(input: string): string {
console.log('doing...');
return `:${input.toUpperCase()}`;
}
aboutToAppear() {
const processedResult: string = this.processData('decorator demo');
console.log('res:', processedResult);
this.message = processedResult;
}
build() {
Column({ space: 30 }) {
Text(this.message)
.fontSize(24)
.margin(10);
Button('fun1')
.onClick(() => this.processData('button click'))
.margin(10);
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center);
}
}
Top comments (0)