Decorator(修飾器)實踐修飾Api

Es6 Decorator(修飾器)

基本摘抄自阮一峯老師Es6入門

1. 類的修飾

  • 爲類添加靜態屬性

    @testable
    class MyTestableClass {
      // ...
    }
    ​
    function testable(target) {
      target.isTestable = true;
    }
    ​
    MyTestableClass.isTestable // true

    帶參數的修飾器

    function testable(isTestable) {
        return function(target) {
            target.isTestable = isTestable;
        }
    }
    ​
    @testable(true)
    class MyTestableClass {}
    MyTestableClass.isTestable // true
    ​
    @testable(false)
    class MyClass {}
    MyClass.isTestable // false

    注意: 修飾器對類的行爲的改變,是代碼編譯時發生的,而不是在運行時

  • 爲類的實例添加屬性

    function testable(target) {
        target.prototype.isTestable = true;
    }
    ​
    @testable
    class MyTestableClass {}
    ​
    let obj = new MyTestableClass();
    obj.isTestable;

    爲類的實例添加方法

    // mixins.js
    export function mixins(...list) {
        return function (target) {
            Object.assign(target.prototype, ...list)
        }
    }
    ​
    // main.js
    import { mixins } from './mixins';
    ​
    const Foo = {
        foo() { console.log('foo') }
    }
    ​
    @mixins(Foo)
    class MyClass {}
    ​
    let obj = new MyClass();
    obj.foo(); // foo

2. 方法的修飾

  • 修飾類的屬性

    class Person {
        @readonly
        name() {
            return `${this.first} ${this.last}`
        }
    }
    ​
    function readonly(target, name, descriptor) {
        // descriptor對象原來的值如下
        // {
        //   value: specifiedFunction,
        //   enumerable: false,
        //   configurable: true,
        //   writable: true
        // };
        descriptor.writable = false;
        return descriptor;
    }
    /* 
    target: 類的原型對象
    name: 當前方法的名字
    descriptor: 承載着裝飾的內容 descriptor.value 就是被修飾的方法
    */
    ​
    readonly(Person.prototype, 'name', descriptor);
    // 類似於
    Object.defineProperty(Person.prototype, 'name', descriptor);
  • 修飾器 @log

    class Math {
      @log
      add(a, b) {
        return a + b;
      }
    }
    ​
    function log(target, name, descriptor) {
      var oldValue = descriptor.value;
    ​
      descriptor.value = function() {
        console.log(`Calling ${name} with`, arguments);
        return oldValue.apply(this, arguments);
      };
    ​
      return descriptor;
    }
    ​
    const math = new Math();
    ​
    // passed parameters should get logged now
    math.add(2, 4);

    注意:如果同一個方法有多個修飾器,會像剝洋蔥一樣,先從外到內進入,然後由內向外執行。

    function dec(id){
      console.log('evaluated', id);
      return (target, property, descriptor) => console.log('executed', id);
    }
    ​
    class Example {
        @dec(1)
        @dec(2)
        method(){}
    }
    // evaluated 1
    // evaluated 2
    // executed 2
    // executed 1

3. 修飾器不能修飾函數

  • 因爲函數存在函數提升,類是不會提升的,所以修飾器不能修飾函數

  • 修飾函數可以採用高階函數的形式

    function doSomething(name) {
        console.log('Hello, ' + name);
    }
    ​
    function loggingDecorator(func) {
        return function() {
            console.log('Starting');
            const result = wrapped.apply(this, arguments);
            console.log('Finished');
            return result;
        }
    }
    ​
    const wrapped = loggingDecorator(doSomething);
    wrapped('wxx');
    // 輸出
    // Starting
    // Hello, wxx
    // Finished

4. Core-decorators.js

  1. @autobind

  2. @readonly

  3. @override

  4. @deprecate

  5. @suppressWarnings

5. 使用修飾器實現自動發佈事件

const postal = require("postal/lib/postal.lodash");
export default function publish(topic, channel) {
    const channelName = channel || '/';
    const msgChannel = postal.channel(channelName);
    msgChannel.subscribe(topic, v=> {
        console.log('頻道: ', channelName);
        console.log('事件: ', topic);
        console.log('數據: ', v);
    })
    return function (target, name, descriptor) {
        const fn = descriptor.value;
        descriptor.value = function() {
            let value = fn.apply(this, arguments);
            msgChannel.publish(topic, channel);
        }
    }
}

用法

// index.js
import publish from './publish';

class FooComponent {
  @publish('foo.some.message', 'component')
  someMethod() {
    return { my: 'data' };
  }
  @publish('foo.some.other')
  anotherMethod() {
    // ...
  }
}

let foo = new FooComponent();

foo.someMethod();
foo.anotherMethod();
// 調用方法時,會自動發送事件,並輸出
/*
頻道: component
事件: foo.some.message
數據: { my: 'data'}
*/

6. Babel 轉換器的支持

安裝@babel/core@babel/plugin-proposal-decorators

設置配置文件.babelrc

{
  "plugins": ["@babel/plugin-proposal-decorators"]
}
require("@babel/core").transform("code", {
  plugins: ["@babel/plugin-proposal-decorators"]
});

 

🌟使用修飾器修飾發送請求的API

1. @response (發送請求成功時)

  • 發送請求成功時的修飾

  • 具體實現

    // 需要傳遞額外參數,所以再封裝一層函數
    export default function response(msg) {
        return function(target, name, descriptor) {
            /* 
            target: 類的原型對象
            name: 當前方法的名字
            descriptor: 承載着裝飾的內容 descriptor.value 就是被修飾的方法
            */
            function createResponse(oldValue) {
                return function() {
                    let returnValue = oldValue.apply(this, arguments);
                    if(!(returnValue instanceof Promise)) {
                        console.warn(`方法${name}返回值不是Promise對象,裝設器無效!`);
                        return returnValue;
                    }
                    return new Promise((resolve, reject) => {
                        returnValue.then(res => {
                            if(res.code == 200) {
                                if(msg) {
                                    // Config.Alert是註冊在Config的實例化的Alert組件
                                    Config.Alert.success(msg, close => {
                                        close();
                                        resolve(res);
                                    })
                                } else {
                                    resolve(res);
                                }
                            } else {
                                Config.Alert.error(res, close => {
                                    close();
                                    reject(res);
                                })
                            }
                        })
                    })
                }
            }
            // 當爲箭頭函數修飾時,需要修飾其屬性
            if(descriptor.initializer) {
                return {
                    get: function() {
                        return createResponse(descriptor.initializer.call(this));
                    }
                }
            }
            // 當爲函數聲明式寫法,需要修飾其方法
            if(descriptor.value) {
                return {
                    value: createResponse(descriptor.value)
                }
            }
            return descriptor;
        }
    }
    

     

2. @retry (發送請求超時)

  • 發送請求超時的後續處理,我們項目中是當請求超時,展示彈框,彈框中有重試按鈕,點擊時將原有請求參數附帶上次請求的requestId

  • 具體實現

    export default function retry() {
        return function(target, name, descriptor) {
            /* 
            target: 類的原型對象
            name: 當前方法的名字
            descriptor: 承載着裝飾的內容 descriptor.value 就是被修飾的方法
            */
            function createRetry(oldValue) {
                let newValue = function() {
                    let returnValue = oldValue.apply(this, arguments);
                    if(!(returnValue instanceof Promise)) {
                        console.warn(`方法${name}返回值不是Promise對象,裝設器無效!`);
                        return returnValue;
                    }
                    if(arguments.length !== 1 && !isPlainObject(arguments[0])) {
                        console.warn(`方法${name}必須有且只有一個參數且是對象,裝設器無效!`);
                        return returnValue;
                    }
                    const formData = arguments[0];
                    const that = this;
                    return new Promise((resolve, reject) => {
                        returnValue.then(res => {
                            if(res.code === 200) {
                                resolve(res);
                            } else if(res.code === 408) {
                                Config.Alert.warning('處理超時,請重試', {
                                    okText: '重試',
                                    cancelText: '稍後處理',
                                    onOk: close => {
                                        const requestId = res.data.code || res.requestId;
                                        const promise = newValue.apply(that, [{...formData, requestId}]);
                                        promise.then(resolve, reject);
                                        close();
                                    },
                                    onCancel: close => {
                                        reject(res);
                                        close();
                                    }
                                })
                            } else {
                                resolve(res);
                            }
                        })
                    })
                };
                return newValue;
            }
            if (descriptor.initializer) {
                return {
                    get: function () {
                        return createRetry(descriptor.initializer.call(this));
                    }
                }
            }
            if (descriptor.value) {
                return {
                    value: createRetry(descriptor.value)
                }
            }
            return descriptor;
        }
           
    }
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章