TypeScript裝飾器(decorators)


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

例:

@Path('/hello')
class HelloService {}

TypeScript中裝飾器還屬於實驗性語法,所以要想使用必須在配置文件中tsconfig.json編譯選項中開啟:

{
    "compilerOptions": {
        "experimentalDecorators": true
    }
}

如何定義裝飾器

裝飾器本身其實就是一個函數,理論上忽略參數的話,任何函數都可以當做裝飾器使用。例:

demo.ts

function Path(target:any) {
    console.log("I am decorator.")
}

@Path
class HelloService {}

使用tsc編譯后,執行命令node demo.js,輸出結果如下:

I am decorator.

裝飾器執行時機

修飾器對類的行為的改變,是代碼編譯時發生的(不是TypeScript編譯,而是js在執行機中編譯階段),而不是在運行時。這意味着,修飾器能在編譯階段運行代碼。也就是說,修飾器本質就是編譯時執行的函數。
在Node.js環境中模塊一加載時就會執行

函數柯里化解決參數問題

但是實際場景中,有時希望向裝飾器傳入一些參數, 如下:

@Path("/hello", "world")
class HelloService {}

此時上面裝飾器方法就不滿足了(VSCode編譯報錯),這是我們可以借助JavaScript中函數柯里化特性

function Path(p1: string, p2: string) {
    return function (target) { //  這才是真正裝飾器
        // do something 
    }
}

五種裝飾器

在TypeScript中裝飾器可以修飾四種語句:類,屬性,訪問器,方法以及方法參數。

1 類裝飾器

應用於類構造函數,其參數是類的構造函數。
注意class並不是像Java那種強類型語言中的類,而是JavaScript構造函數的語法糖。

function Path(path: string) {
    return function (target: Function) {
        !target.prototype.$Meta && (target.prototype.$Meta = {})
        target.prototype.$Meta.baseUrl = path;
    };
}

@Path('/hello')
class HelloService {
    constructor() {}
}

console.log(HelloService.prototype.$Meta);// 輸出:{ baseUrl: '/hello' }
let hello = new HelloService();
console.log(hello.$Meta) // 輸出:{ baseUrl: '/hello' }
2 方法裝飾器

它會被應用到方法的 屬性描述符上,可以用來監視,修改或者替換方法定義。
方法裝飾會在運行時傳入下列3個參數:

  • 1、對於靜態成員來說是類的構造函數,對於實例成員是類的原型對象。
  • 2、成員的名字。
  • 3、成員的屬性描述符。
function GET(url: string) {
    return function (target, methodName: string, descriptor: PropertyDescriptor) {
        !target.$Meta && (target.$Meta = {});
        target.$Meta[methodName] = url;
    }
}

class HelloService {
    constructor() { }
    @GET("xx")
    getUser() { }
}

console.log((<any>HelloService).$Meta);

注意:在vscode編輯時有時會報作為表達式調用時,無法解析方法修飾器的簽名。錯誤,此時需要在tsconfig.json中增加target配置項:

{
    "compilerOptions": {
        "target": "es6",
        "experimentalDecorators": true,
    }
}
3 方法參數裝飾器

參數裝飾器表達式會在運行時當作函數被調用,傳入下列3個參數:

  • 1、對於靜態成員來說是類的構造函數,對於實例成員是類的原型對象。
  • 2、參數的名字。
  • 3、參數在函數參數列表中的索引。
function PathParam(paramName: string) {
    return function (target, methodName: string, paramIndex: number) {
        !target.$Meta && (target.$Meta = {});
        target.$Meta[paramIndex] = paramName;
    }
}

class HelloService {
    constructor() { }
    getUser( @PathParam("userId") userId: string) { }
}

console.log((<any>HelloService).prototype.$Meta); // {'0':'userId'}
4 屬性裝飾器

屬性裝飾器表達式會在運行時當作函數被調用,傳入下列2個參數:

  • 1、對於靜態成員來說是類的構造函數,對於實例成員是類的原型對象。
  • 2、成員的名字。
function DefaultValue(value: string) {
    return function (target: any, propertyName: string) {
        target[propertyName] = value;
    }
}

class Hello {
    @DefaultValue("world") greeting: string;
}

console.log(new Hello().greeting);// 輸出: world

裝飾器加載順序

function ClassDecorator() {
    return function (target) {
        console.log("I am class decorator");
    }
}
function MethodDecorator() {
    return function (target, methodName: string, descriptor: PropertyDescriptor) {
        console.log("I am method decorator");
    }
}
function Param1Decorator() {
    return function (target, methodName: string, paramIndex: number) {
        console.log("I am parameter1 decorator");
    }
}
function Param2Decorator() {
    return function (target, methodName: string, paramIndex: number) {
        console.log("I am parameter2 decorator");
    }
}
function PropertyDecorator() {
    return function (target, propertyName: string) {
        console.log("I am property decorator");
    }
}

@ClassDecorator()
class Hello {
    @PropertyDecorator()
    greeting: string;


    @MethodDecorator()
    greet( @Param1Decorator() p1: string, @Param2Decorator() p2: string) { }
}

輸出結果:

I am property decorator
I am parameter2 decorator
I am parameter1 decorator
I am method decorator
I am class decorator

從上述例子得出如下結論:

1、有多個參數裝飾器時:從最后一個參數依次向前執行

2、方法和方法參數中參數裝飾器先執行。

3、類裝飾器總是最后執行。

4、方法和屬性裝飾器,誰在前面誰先執行。因為參數屬於方法一部分,所以參數會一直緊緊挨着方法執行。上述例子中屬性和方法調換位置,輸出如下結果:

I am parameter2 decorator
I am parameter1 decorator
I am method decorator
I am property decorator
I am class decorator


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM