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
-
@autobind
-
@readonly
-
@override
-
@deprecate
-
@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; } }