HarmonyOS鸿蒙Next中装饰器原理是什么?如何自定义装饰器,请提供步骤说明

HarmonyOS鸿蒙Next中装饰器原理是什么?如何自定义装饰器,请提供步骤说明 在ArkTS中装饰器(Decorator)是一种特殊的声明,能够对类、方法、属性等进行标注和修改。

因为ArkTS 是TypeScript 扩展而来的编程语言,TypeScript 支持装饰器特性。它属于元编程的一种工具,可在不改变原有代码结构的基础上,为其添加额外的功能。比如在鸿蒙开发里,装饰器能够用来定义组件的属性、生命周期方法等。像@Component装饰器就用于把一个类标记成鸿蒙的组件类。

鸿蒙中的装饰器有状态装饰器V1和V2(@State@Prop@Link@ObservedV2等等)。组件装饰器@Entry@CustomDialog@Component@Builder等等。

请问鸿蒙的装饰器原理是什么?如何自定义装饰器,请提供步骤说明


更多关于HarmonyOS鸿蒙Next中装饰器原理是什么?如何自定义装饰器,请提供步骤说明的实战教程也可以访问 https://www.itying.com/category-93-b0.html

4 回复

一、装饰器的基本原理

ArkTS通过装饰器的方式,调用函数实现。在不侵入原有代码结构的基础上,进行扩展。

装饰器一般分为三种:类装饰器,方法装饰器,属性装饰器。

类装饰器在类之上声明,接收类的构造函数作为参数,可用于修改类的构造函数、添加类的属性和方法,或者对类进行一些元数据的标注:

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

方法装饰器应用于类的方法,在方法被定义时执行。它接收三个参数:目标对象、方法名和方法描述符。方法装饰器可以用于修改方法的行为,比如添加日志、进行权限验证、实现节流防抖等功能。在OpenHarmony开源系统中,对照系统相机源码,可看到以下自定义方法类如下所示。但是在HarmonyOS中,ArkTS对any强类型校验不通过。目前这种写法无法使用。

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

属性装饰器应用于类的属性,在属性被定义时执行。它接收两个参数:目标对象和属性名。属性装饰器可以用于修改属性的访问器,比如添加属性验证逻辑、实现属性的缓存等。 但是在HarmonyOS中,ArkTS对any强类型校验不通过。目前这种写法无法使用。

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; // 会抛出异常

二、在HarmonyOS中如何自定义装饰器

综上所述,在HarmonyOS中有特殊的ArkTS语法规则,any unknown这些不能使用。所以我们需要通过Object代替targe的any类型。

PropertyDescriptor中的value值也是any,直接按照ts的语法是没问题。但是在ArkTS中我们需要曲线实现目标,通过Function的形式获取value属性的值,再将target: Object, key: string, descriptor: PropertyDescriptor通过…args的形式赋值。

...args 是 JavaScript 和 TypeScript(ArkTS 基于 TypeScript)中的剩余参数(Rest Parameters)语法。

在HarmonyOS中定义类装饰器的方式如下所示,自定义属性装饰器同理:

// 自定义方法装饰器:记录方法调用信息
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('正在处理数据...');
    return `处理后的数据:${input.toUpperCase()}`;
  }

  // 组件显示时触发方法调用
  aboutToAppear() {
    const processedResult: string = this.processData('decorator demo');
    console.log('最终结果:', processedResult);
    this.message = processedResult;
  }

  build() {
    Column({ space: 30 }) {
      Text(this.message)
        .fontSize(24)
        .margin(10);

      Button('点击触发方法')
        .onClick(() => this.processData('button click'))
        .margin(10);
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center);
  }
}

更多关于HarmonyOS鸿蒙Next中装饰器原理是什么?如何自定义装饰器,请提供步骤说明的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


装饰器设计模式

鸿蒙:貌似现在只能对方法增强

// 接口描述
// interface PropertyDescriptor {
//   configurable?: boolean;   // 是否可配置(如删除/修改特性)
//   enumerable?: boolean;     // 是否可枚举(如for-in循环)
//   value?: any;              // 属性值(数据属性专用)
//   writable?: boolean;       // 是否可写(数据属性专用)
//   get?(): any;              // getter方法(访问器属性专用)
//   set?(v: any): void;       // setter方法(访问器属性专用)
// }

// 描述
function 装饰器名(
  target: Object,       // 第1个参数:被装饰方法所属的「宿主对象」
  key: string,          // 第2个参数:被装饰方法的「名称」
  descriptor: PropertyDescriptor // 第3个参数:被装饰方法的「属性描述符」
) { /* 装饰逻辑 */ }

export function Login(target: Object, key: string, descriptor: PropertyDescriptor) {
  // 1. 保存原方法的引用(避免覆盖后丢失原逻辑)
  const originalMethod: Function = descriptor.value

  // 2. 重写方法的实现逻辑
  descriptor.value = (...args: Object[]) => {
    // 取值
    const name = args[0]
    // 2.1 新增的前置逻辑:打印日志--调用原方法前调用逻辑
    console.info('汉堡黄111')

    // 2.2 执行原方法,并传递所有入参(...args 是剩余参数,接收原方法的所有参数)
    const result: Object = originalMethod(...args)

    // 2.1 新增的前置逻辑:打印日志--调用原方法后调用逻辑
    console.info('汉堡黄222')

    // 2.3 返回原方法的执行结果(保证原方法的返回值不变)
    return result
  }

  // 3. 返回修改后的属性描述符(装饰器要求返回 descriptor 以生效)
  return descriptor
}

// 页面使用
import { listDragAndDrop1 } from '../components/case3';
import { Login } from '../utlis/testCustom';

@Entry
@Component
struct Index {
  @State message: string = 'Hello World';


  @Login
  handleLogin() {
    const name = '汉堡黄333'
    console.info('name', name)
  }

  build() {
    Column() {
      Button('dad').onClick((event: ClickEvent) => {
        this.handleLogin()
      })
    }
    .height('100%')
    .width('100%')
  }
}

装饰器是鸿蒙Next中基于ArkTS的元编程特性,用于扩展类、方法、属性或参数的行为。其原理是在编译时或运行时通过包装目标元素来注入额外逻辑,实现关注点分离。

自定义装饰器步骤:

  1. 定义装饰器函数,接收目标元素相关参数。
  2. 在函数内实现装饰逻辑,可修改或包装原始行为。
  3. 使用@装饰器名语法应用到类、方法或属性上。

例如,定义一个记录方法执行时间的装饰器:

function logTime(target: any, methodName: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.time(methodName);
    const result = originalMethod.apply(this, args);
    console.timeEnd(methodName);
    return result;
  };
}

HarmonyOS Next中ArkTS装饰器的核心原理是基于TypeScript装饰器的元编程机制,在编译时对类、方法、属性或参数进行注解和转换,为其注入框架所需的特定逻辑。

装饰器原理: 装饰器本质上是一个函数,在编译阶段被调用。以类装饰器为例,当编译器检测到@Decorator语法时,会将被装饰的类(或方法、属性)的描述信息作为参数传递给装饰器函数。该函数可以返回一个新的构造器或修改原构造器的原型,从而改变类的默认行为。在HarmonyOS中,框架内置的装饰器(如@Component@State)会为类添加鸿蒙组件的生命周期管理、状态绑定等底层能力,这些转换后的代码最终会被编译为ArkUI的声明式UI运行时所能识别的格式。

自定义装饰器步骤:

  1. 定义装饰器函数:编写一个高阶函数,其参数根据装饰目标类型(类、方法、属性)而定。例如,一个简单的属性装饰器函数接收target(类原型)、propertyKey(属性名)等参数。
  2. 实现装饰逻辑:在函数内部添加所需的元数据标记、属性劫持或原型修改逻辑。例如,可以通过Reflect.defineMetadata(需引入reflect-metadata库)存储元信息,或在get/set访问器中添加拦截逻辑。
  3. 应用装饰器:使用@YourDecorator语法标注目标类、方法或属性。确保tsconfig.json中已启用experimentalDecoratorsemitDecoratorMetadata编译选项。
  4. 结合运行时逻辑:自定义装饰器通常需要与框架的其他部分(如生命周期、状态管理)协同工作。例如,可以定义一个@Log方法装饰器,在方法执行前后输出日志,这需要封装原始方法并替换原型方法。

示例(方法装饰器):

function Log(target: any, methodName: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    descriptor.value = function (...args: any[]) {
        console.log(`调用方法 ${methodName},参数:`, args);
        const result = originalMethod.apply(this, args);
        console.log(`方法 ${methodName} 结果:`, result);
        return result;
    };
}

class MyComponent {
    @Log
    calculate(x: number, y: number): number {
        return x + y;
    }
}

此装饰器在方法执行前后添加了日志输出,展示了如何通过修改描述符来增强原有功能。自定义时需注意装饰器的执行顺序和框架兼容性。

回到顶部