(精華)2020年8月2日 TypeScript 裝飾器的使用

愚公搬程式碼發表於2020-08-02

裝飾器

裝飾器:裝飾器是一種特殊型別的宣告,它能夠被附加到類宣告,方法,屬性或引數上,可以修改類的行為。
通俗的講裝飾器就是一個函式方法,可以注入到類、方法、屬性引數上來擴充套件類、屬性、方法、引數的功能,
可以認為就是在原有程式碼外層包裝了一層處理邏輯。

裝飾器在身邊的例子隨處可見

水龍頭上邊的起泡器就是一個裝飾器,在裝上以後就會把空氣混入水流中,摻雜很多泡泡在水裡。
但是起泡器安裝與否對水龍頭本身並沒有什麼影響,即使拆掉起泡器,也會照樣工作,水龍頭的作用在於閥門的控制,至於水中摻不摻雜氣泡則不是水龍頭需要關心的。

在TypeScript中裝飾器還屬於實驗性語法,你必須在命令列或tsconfig.json裡啟用experimentalDecorators編譯器選項:
“experimentalDecorators”: true

裝飾器的寫法:普通裝飾器(無法傳參) 、 裝飾器工廠(可傳參)

裝飾器是過去幾年中js最大的成就之一,已是Es7的標準特性之一

為什麼要用裝飾器

可能有些時候,我們會對傳入引數的型別判斷、對返回值的排序、過濾,對函式新增節流、防抖或其他的功能性程式碼,基於多個類的繼承,各種各樣的與函式邏輯本身無關的、重複性的程式碼。
所以,對於裝飾器,可以簡單地理解為是非侵入式的行為修改。

使用裝飾器的一個重要規則

要用裝飾器擴充套件功能而不是覆蓋原來的功能

裝飾器的作用

讓人更加關注業務程式碼的開發,封裝功能輔助性的程式碼。重點就是讓人把焦點放在業務上,實現焦點分離。

如何定義裝飾器

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

helloword.ts


function helloWord(target: any) {
    // target.username = 'dddd';
    console.log('hello Word!');
}

@helloWord
class HelloWordClass {
    // static username:string;
}
var p1 = new HelloWordClass();

裝飾器執行時機

修飾器對類的行為的改變,是程式碼編譯時發生的(不是TypeScript編譯,而是js在執行機中編譯階段),而不是在執行時。這意味著,修飾器能在編譯階段執行程式碼。也就是說,修飾器本質就是編譯時執行的函式

裝飾器型別

常見的裝飾器有:類裝飾器、屬性裝飾器、方法裝飾器、引數裝飾器

1 類裝飾器

類裝飾器在類宣告之前被宣告(緊靠著類宣告)。 類裝飾器應用於類建構函式,可以用來監視,修改或替換類定義。
傳入一個引數:類的建構函式

1.1 普通裝飾器(無法傳參)


function logClass(target:any){
    console.log(target);
    // target 就是當前類
    target.prototype.apiUrl='動態擴充套件的屬性';
    target.prototype.run=function(){
        console.log('我是一個run方法');
    }
}

@logClass
class HttpClient{
    constructor(){
    }
    getData(){

    }
}
var http:any=new HttpClient();
console.log(http.apiUrl);
http.run();

如果需要向裝飾器裡傳參,可以藉助JavaScript中函式柯里化特性

1.2 類裝飾器:裝飾器工廠(可傳參)

 function logClass(params:string){
         // target 就是當前類 ,params是裝飾器傳過來的引數
        return function(target:any){   //  這才是真正裝飾器
            console.log(target);
            console.log(params);
            target.prototype.apiUrl=params;
        }
    }
    // @logClass('http://ruanmou.com/api') 是一個立即執行函式, 其實返回的是 logClass裡面的閉包函式

    @logClass('http://ruanmou.com/api')
    class HttpClient{
        constructor(){
        }
        getData(){
        }
    }
    var http:any=new HttpClient();
    console.log(http.apiUrl);

2. 屬性裝飾器

屬性裝飾器表示式會在執行時當作函式被呼叫,給屬性賦值
傳入下列2個引數:
1、對於靜態成員來說是類的建構函式,對於例項成員是類的原型物件。
2、成員的名字。

 //類裝飾器,可以傳參
    function logClass(params:string){
        return function(target:any){
            console.log(target);
            console.log(params);       
        }
    }

    //屬性裝飾器
    function logProperty(params:any){
        
        console.log(params);  //ruanmou.com
        return function(target:any,attr:any){
            console.log(target);  //HttpClient的 原型方法
            console.log(attr);  // 使用裝飾器的那個屬性,url
            target[attr]=params;
        }
    }
    @logClass('xxxx')
    class HttpClient {
        @logProperty('ruanmou.com')
        public url:any |undefined;
        // @logProperty('laney')
        public name:string | undefined;
        constructor(str:string){
            // this.name = str;
        }
        getData(){
            console.log(this.url);
        }
        say(){
            console.log(this.name);
        }
    }

    var http=new HttpClient('hello');

    http.getData();

3. 方法裝飾器

它會被應用到方法的屬性描述符上,可以用來監視,修改或者替換方法定義。

方法裝飾會在執行時傳入下列3個引數:
1、對於靜態成員來說是類的建構函式,對於例項成員是類的原型物件。
2、成員的名字。
3、成員的屬性描述符。

3.1 方法裝飾器一

function get(params:any){
        return function(target:any,methodName:any,desc:any){
            
            console.log(target);
            console.log(methodName);
            console.log(desc);
            target.apiUrl='xxxx';
            target.run=function(){
                console.log('run');
            }
        }
    }

    class HttpClient{  
        public url:any |undefined;
        constructor(){
        }
        @get('http://www.baidu.com')
        getData(){
            console.log(this.url);
        }
    }

    var http:any=new HttpClient();
    console.log(http.apiUrl);
    http.run();

3.2 方法裝飾器二

function get(params:any){
    return function(target:any,methodName:any,desc:any){
        console.log(target);
        console.log(methodName);
        console.log(desc.value);       
        
        //修改裝飾器的方法  把裝飾器方法裡面傳入的所有引數改為string型別

        //1、儲存當前的方法

        var oMethod=desc.value;
        desc.value=function(...args:any[]){                
            args=args.map((value)=>{
                return String(value);
            })
            oMethod.apply(this,args);
        }

    }
}

class HttpClient{  
    public url:any |undefined;
    constructor(){
    }
    @get('http://www.baidu.com')
    getData(...args:any[]){
        console.log(args);
        console.log('我是getData裡面的方法');
    }
}

var http=new HttpClient();
http.getData(123,'xxx');

4、引數裝飾器

引數裝飾器表示式會在執行時當作函式被呼叫,可以使用引數裝飾器為類的原型增加一些元素資料 ,
傳入下列3個引數:

1、對於靜態成員來說是類的建構函式,對於例項成員是類的原型物件。
2、方法的名字。
3、引數在函式引數列表中的索引。

function logParams(params: any) {
    // 1、對於靜態成員來說是類的建構函式,對於例項成員是類的原型物件。
    // 2、方法的名字。
    // 3、引數在函式引數列表中的索引。
    return function (target: any, methodName: any, paramsIndex: any) {
    //   console.log(params);
    //   console.log(target);
    //   console.log(methodName);
    //   console.log(paramsIndex);
      target.apiUrl = params;
    };
  }

  class HttpClient {
    public url: any | undefined;
    constructor() {}
    getData(@logParams("3yteam.com") uuid: any) {
      console.log(uuid);
    }
  }

  var http: any = new HttpClient();
  http.getData(123456);
  console.log(http.apiUrl);

訪問器裝飾器

訪問器裝飾器應用於訪問器的屬性描述符,可用於觀察,修改或替換訪問者的定義。 訪問器裝飾器不能在宣告檔案中使用,也不能在任何其他環境上下文中使用(例如在宣告類中)

注意: TypeScript不允許為單個成員裝飾get和set訪問器。相反,該成員的所有裝飾器必須應用於按文件順序指定的第一個訪問器。這是因為裝飾器適用於屬性描述符,它結合了get和set訪問器,而不是單獨的每個宣告。

訪問器裝飾器表示式會在執行時當作函式被呼叫,傳入下列3個引數:

  1. 對於靜態成員來說是類的建構函式,對於例項成員是類的原型物件。
  2. 成員的名字。
  3. 成員的屬性描述符。

下面是使用了訪問器裝飾器(@configurable)的例子,應用於Point類的成員上:

function configurable(value: boolean) {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        descriptor.configurable = value;
    };
}

 class Point {
    private _x: number;
    private _y: number;
    constructor(x: number, y: number) {
        this._x = x;
        this._y = y;
    }

    @configurable(false)
    get x() { return this._x; }

    @configurable(false)
    get y() { return this._y; }
}

裝飾器執行順序

屬性》方法》方法引數》類

如果有多個同樣的裝飾器,它會先執行後面的

function logClass1(params:string){
        return function(target:any){
          console.log('類裝飾器1')
        }
    }
    
    function logClass2(params:string){
        return function(target:any){
          console.log('類裝飾器2')
        }
    }
    
    function logAttribute1(params?:string){
        return function(target:any,attrName:any){
          console.log('屬性裝飾器1')
        }
    }
    
    function logAttribute2(params?:string){
        return function(target:any,attrName:any){
          console.log('屬性裝飾器2')
        }
    }
    
    function logMethod1(params?:string){
        return function(target:any,attrName:any,desc:any){
          console.log('方法裝飾器1')
        }
    }
    function logMethod2(params?:string){
        return function(target:any,attrName:any,desc:any){
          console.log('方法裝飾器2')
        }
    }
     
    function logParams1(params?:string){
        return function(target:any,attrName:any,desc:any){
          console.log('方法引數裝飾器1')
        }
    }
    
    function logParams2(params?:string){
        return function(target:any,attrName:any,desc:any){
          console.log('方法引數裝飾器2')
        }
    }
    
    @logClass1('http://www.3yteam.com/api')
    @logClass2('xxxx')
    class HttpClient{
        @logAttribute1()
        @logAttribute2()
        public apiUrl:string | undefined;
        constructor(){
        }
    
        @logMethod1()
        @logMethod2()
        getData(){
            return true;
        }
    
        setData(@logParams1() attr1:any,@logParams2() attr2:any,){
    
        }
    }
    
    var http:any=new HttpClient();

相關文章