一文搞懂 TypeScript 裝飾器

前段時間爲了開發 HarmonyOS 路由框架,學習了 TypeScript 裝飾器。在學習中發現無論是官方文檔還是線上資料,鮮有文章可以將裝飾器簡單通俗的表達清楚,故而萌生了撰寫此文的想法。

當然,作爲一個前端門外漢、TS 初學者,寫這篇文字更主要的還是爲了加深自己對裝飾器的理解。新手上路,難免有不足之處,還請各位看官輕噴。

一. 什麼是裝飾器?

官方文檔上的解釋是:

裝飾器是一種特殊類型的聲明,它能夠被附加到類聲明,方法,訪問符,屬性或參數上。裝飾器使用 @表達式這種形式,表達式求值後必須爲一個函數,會在運行時當被調用時,被裝飾的聲明信息做爲參數傳入。

這段官方解釋了幾個信息:

  1. 裝飾器用在類、屬性、方法或者參數等之上;

    注:可修改、替換被裝飾的目標,也可已在不影響原有特性的前提下增加附屬功能。

  2. 使用 @+ 函數 (或者說表達式) 的形式來書寫;

  3. 該函數在運行時被調用,函數入參通常爲與被裝飾對象相關的一些信息;

  4. 這個函數或者不返回值,或者返回一個可以替換被裝飾的目標的對象。(這一點官方解釋裏未說明)

示例代碼如下:

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

@MyDecorator
class User{
  ...
}

熟悉Java的同學,現在應該發現了,這不就是 java 中的註解嗎!不同的是 TypeScript 裝飾器只在運行時被調用,而Java中的註解能作用於源碼階段、編譯階段和運行時。

二. 裝飾器的分類

裝飾器的分類從寫法上來區分,可分爲:

從類別上來區分,TypeScript 中裝飾器主要分爲以下五類:

前四類裝飾器比較常用,後面的章節我們將分別介紹它們的定義以及實現方法、應用場景。

三. 裝飾器的寫法

這裏用兩段代碼來分別演示普通裝飾器和裝飾器工廠的寫法,它們的主要區別是:「普通裝飾器不可帶參數,而裝飾器工廠可以帶參數。」

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),也不能用在任何外部上下文中(比如聲明的類)

  • 類裝飾器表達式會在運行時調用函數,類的構造函數作爲其唯一的參數。

  • 如果類裝飾器返回一個值,它會使用提供的重構函數來替換類的聲明。

簡單來說,類裝飾器是直接作用在類裝飾器上的,類裝飾器的類型描述如下:

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

它的參數只有一個:

示例代碼:

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),或者任何外部上下文(比如聲明的類)裏。

  • 屬性裝飾器表達式會在運行時調用函數,設置以下 2 個參數:

  1. 對於靜態成員來說是類的構造函數,對於實例成員來說是類的原型對象。

  2. 成員的名字。

屬性裝飾器可以定義在類的屬性上,它的類型描述如下:

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

它的參數有兩個:

示例代碼:

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),重載或者任何外部上下文(比如聲明的類)中;

  • 裝飾器的方法表達式會在運行時調用函數,並確定以下 3 個參數:

  1. 對於靜態成員來說是類的構造函數,對於實例成員來說是類的原型對象。

  2. 成員的名字。

  3. 成員的屬性接口

  • 如果方法裝飾器返回一個值,它會被初始化方法的屬性控件。

裝飾器顧名思義就是定義在類中方法上的裝飾器,他的類型描述如下:

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

它接收三個參數:

示例代碼如下:

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)中,重載或其他外部上下文(比如聲明的類)裏。

  • 參數裝飾器表達式會在運行時調用函數,確定以下 3 個參數:

  1. 對於靜態成員來說是類的構造函數,對於實例成員來說是類的原型對象。

  2. 成員的名字。

  3. 參數在函數參數列表中的索引。

  • 參數裝飾器只能用於監視一個方法的參數是否被確定。

  • 參數裝飾器的返回值會被忽略。

裝飾器參數定義在方法參數上,其類型描述如下:

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

參數裝飾器接收三個參數:

示例代碼:

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);
  }
}
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/KkvXAT3prb3DFfsNuak8gg