一文搞懂 TypeScript 装饰器

本文主要讲解关于一文搞懂 TypeScript 装饰器相关内容,让我们来一起学习下吧!

本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!

一文搞懂 TypeScript 装饰器

前段时间为了开发 HarmonyOS 路由框架,学习了 TypeScript 装饰器。在学习中发现无论是官方文档还是线上资料,鲜有文章能将装饰器简单通俗的表述清楚,故而萌生了撰写此文的想法。

当然,作为一个前端门外汉、TS初学者,写这篇文字更主要的还是为了加深自己对装饰器的理解。新手上路,难免有不足之处,还请各位看官轻喷。

一. 什么是装饰器?

官方文档上的解释是:

装饰器是一种特殊类型的声明,它能够被附加到类声明,方法, 访问符,属性或参数上。 装饰器使用 @expression 这种形式,expression 求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。

这段官方解释给了几个信息:

  1. 装饰器用在类、属性、方法或者参数等之上;

    注:可修改、替换被装饰的目标,也可已在不影响原有特性的前提下增加附属功能。

  2. 使用@+函数(或者说表达式)的形式来书写;

  3. 这个函数在运行时间被调用,函数入参通常为与被装饰对象相关的一些信息;

  4. 这个函数要么不返回值,要么返回一个可以对象来替换被装饰的目标。(这一点是官方解释里未说明)

示例代码如下:

function MyDecorator(target: any) {
  ...
}

@MyDecorator
class User{
  ...
}

熟悉Java的同学,现在应该发现了,这不就是 java 中的注解吗!不同的是 TypeScript 装饰器只在运行时被调用,而 Java 中的注解能作用于源码阶段、编译阶段和运行时。

二. 装饰器的分类

装饰器的分类从写法上来区分,可分为:

  • 普通装饰器(不可带参数)
  • 装饰器工厂(可以带参数)

从类别上来区分,TypeScript 中装饰器主要分为以下五类:

  • 类装饰器 ClassDecorator
  • 属性装饰器 PropertyDecorator
  • 方法装饰器 MethodDecorator
  • 参数装饰器 ParammeterDecorator
  • 访问符装饰器 AccessorDecorator

前四类装饰器较常用,后面的章节我们将分别介绍它们的定义以及实现方法、应用场景。

三. 装饰器的写法

这里用两段代码来分别演示普通装饰器和装饰器工厂的写法,他们的主要区别是:普通装饰器不可带参数,而装饰器工厂可以带参数。

3.1 普通装饰器

直接将函数作为装饰器函数,我们称之为普通装饰器。

/**
 * 类装饰器(普通装饰器写法)
 */
export function Route(target: object) {
  //TODO
  ...
}
  
//=========================================================
  
/**
 * 使用 Route 装饰器:
 */
@Route()
struct DetailPage {
  build() {
    ...
  }
}

3.2 装饰器工厂

装饰器工厂函数写法,可以在使用时传参,例如下面的 routePath 就是入参。

/**
 * 类装饰器(装饰器工厂实现)
 */
export function Route(routePath: string): ClassDecorator {
  return (target: object) => {
    //TODO
    ...
  }
}

//=========================================================
 
/**
 * 使用 Route 装饰器:
 */
@Route({ routePath: '/jump/entry/detail'})
struct DetailPage {
  build() {
    ...
  }
}

在装饰器工厂函数中,首先会执行Route() ,然后在使用返回的匿名函数作为装饰器的实际逻辑。

三. 装饰器分类

3.1 类装饰器 (ClassDecorator)

类装饰器在类声明之前被声明(紧靠着类声明);类装饰器应用于类构造函数,可以用来监视、修改或替换类定义。

  • 类装饰器不能用在声明文件中( .d.ts ),也不能用在任何外部上下文中(比如 declare 的类)
  • 类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数。
  • 如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明。

简单来说,类装饰器是直接作用在类上的装饰器,类装饰器的类型描述如下:

type ClassDecorator = <TFunction extends Function>(
  target: TFunction
) => TFunction | void;

它的参数只有一个:

  • target:对于类装饰器,target 就是这个类本身,或者说是类的构造函数。

示例代码:

function CustomClassDecorator(info: string): ClassDecorator {
  return (target: Function) => {
    console.log(target) // [Function user]
    console.log(info) // 你好
  }
}

@CustomClassDecorator('你好')
class User {
  public name!: string

  constructor() {
    this.name = '马东锡'
  }
}

3.2 属性装饰器 (PropertyDecorator)

属性装饰器声明在一个属性声明之前(紧靠着属性声明)。

  • 属性装饰器不能用在声明文件中(.d.ts),或者任何外部上下文(比如 declare 的类)里。
  • 属性装饰器表达式会在运行时当作函数被调用,传入下列2个参数:
    1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
    2. 成员的名字。

属性装饰器可以定义在类的属性上,它的类型描述如下:

type PropertyDecorator = (
  target: Object,
  propertyName: string | symbol
) => void;

它的参数有两个:

  • target:对于属性装饰器来说,装饰器被应用在类的实例属性上。在这种情况下,target 参数是一个空对象 {},表示在装饰器内部访问的对象,即被装饰的类的实例
  • propertyName:属性名

示例代码:

function CustomPropertyDecorator(userName: string): PropertyDecorator {
  return (target: Object,
          propertyName: string | symbol) => {

    console.log(target); // {}
    console.log(propertyName); // userName

    targetClassPrototype[propertyName] = userName
  }
}

class User {
  @CustomPropertyDecorator('马东锡')
  public userName!: string

  constructor() {

  }
}

let user = new User()
console.log(user.userName) // 马东锡

3.3 方法装饰器 (MethodDecorator)

方法装饰器声明在一个方法的声明之前(紧靠着方法声明)。 它会被应用到方法的属性描述符上,可以用来监视,修改或者替换方法定义。

  • 方法装饰器不能用在声明文件( .d.ts ),重载或者任何外部上下文(比如 declare 的类)中;
  • 方法装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
    1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
    2. 成员的名字。
    3. 成员的属性描述符
  • 如果方法装饰器返回一个值,它会被用作方法的属性描述符。

方法装饰器顾名思义就是定义在类中方法上的装饰器,他的类型描述如下:

type MethodDecorator = <T>(
  target: Object,
  methodName: string | symbol,
  propertyDescriptor: TypedPropertyDescriptor<T>
) => TypedPropertyDescriptor<T> | void;

它接收三个参数:

  • target:对于方法装饰器和属性装饰器,target 参数是一个对象,表示类的原型,其属性是被装饰的方法。因此,target 参数实际上是指向了被装饰方法的引用
  • methodName: 方法名
  • propertyDescriptor: 属性描述符

示例代码如下:

function CustomMethodDecorator(info: string): MethodDecorator {
  return (target: Object,
          methodName: any,
          propertyDescriptor: PropertyDescriptor) => {
    console.log(target) // { sayHello: [Function (anonymous)] }
    console.log(methodName) //sayHello

    let originMethod = propertyDescriptor.value

    propertyDescriptor.value = function (...args: any) {
      console.log("before")
      console.log("我" + info + "来了") //我马东锡来了
      originMethod.call(this, args)
      console.log("after")
    }
  }
}

class User {
  @CustomMethodDecorator('马东锡')
  sayHello() {
    console.log('执行sayHello()方法)')
  }
}

3.4 参数装饰器 ParammeterDecorator

参数装饰器声明在一个参数声明之前(紧靠着参数声明)。 参数装饰器应用于类构造函数或方法声明。

  • 参数装饰器不能用在声明文件(.d.ts),重载或其它外部上下文(比如 declare 的类)里。
  • 参数装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
    1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
    2. 成员的名字。
    3. 参数在函数参数列表中的索引。
  • 参数装饰器只能用来监视一个方法的参数是否被传入。
  • 参数装饰器的返回值会被忽略。

参数装饰器定义在方法参数上,它的的类型描述如下:

type ParameterDecorator = (
  target: Object,
  methodName: string | symbol,
  parameterIndex: number
) => void;

参数装饰器接收三个参数:

  • target:含义同方法装饰器
  • methodName

    • 如果参数修饰器所在的方法为类的静态/实例方法时,此参数为当前参数修饰器所在方法的方法名。
    • 如果参数修饰器所在的方法为类的构造函数参数修饰时,此参数为 undefined。
  • parameterIndex:参数下标

示例代码:

function CustomParameterDecorator(tag: string): ParameterDecorator {
  return (target: any,
          methodName: string | symbol,
          parameterIndex: number) => {

    console.log(tag); // 装饰实例方法的参数
    console.log(target); // { sayHello: [Function (anonymous)] }
    console.log(methodName.toString()); // sayHello
    console.log(parameterIndex.toString()); // 0

  }
}

class User {
  constructor() {

  }

  sayHello(@CustomParameterDecorator("装饰实例方法的参数") name: String) {
    console.log("你好," + name);
  }
}

以上就是关于一文搞懂 TypeScript 装饰器相关的全部内容,希望对你有帮助。欢迎持续关注程序员导航网,学习愉快哦!

版权声明:juejinhot 发表于 2024-05-12 18:25:32。
转载请注明:一文搞懂 TypeScript 装饰器 | 程序员导航网

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...