由於裝飾器屬於一個在提案中的語法,因此無論是node仍是瀏覽器,如今都沒有直接支持這個語法,咱們要想使用該語法,就必需要經過babel將它進行一個編譯轉換,因此咱們須要搭建一個babel編譯環境。javascript
一、安裝babel相關包java
npm i @babel/cli @babel/core @babel/plugin-proposal-decorators @babel/preset-env -D
二、在項目根目錄下建立.babelrc
node
{ "presets": [ "@babel/preset-env" ], "plugins": [ [ "@babel/plugin-proposal-decorators", { "legacy": true } ] ] }
基礎環境搭建好之後,接下來咱們就能夠盡情的使用裝飾器了react
類裝飾器,顧名思義就是用來裝飾整個類的,能夠用來修改類的一些行爲。shell
// src/demo01.js // 類裝飾器的簡單應用 function log(target) { console.log('target: ', target); } @log class App { }
編譯,執行npm
// 使用babel編譯,將代碼編譯輸出到dist文件夾 npx babel src/demo01.js -d dist // 執行編譯後的代碼 node dist/demo01.js
// 編譯後的代碼 "use strict"; var _class; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } // src/demo01.js // 類裝飾器的簡單應用 function log(target) { console.log('target: ', target); } var App = log(_class = function App() { _classCallCheck(this, App); }) || _class;
這是babel編譯後的源代碼,其實babel加了一下額外的邏輯,刪掉這些邏輯後,裝飾器轉換後的代碼實際上是下面這樣子的:json
function log(target) { console.log('target: ', target); } class App {}; log(App);
執行輸出:redux
target: [Function: App]
能夠看到其實類裝飾器就是一個函數,接受一個類做爲參數,裝飾器函數內部的target參數就是被裝飾的類自己,咱們能夠在裝飾器函數內部對這個類進行一些修改,好比:添加靜態屬性,給原型添加函數等等。瀏覽器
帶參數的裝飾器,須要在外面再套一層接受參數的函數,像下面這樣:babel
// src/demo02.js function log(msg) { console.log('msg: ', msg); return function(target) { console.log('target: ', target); target.msg = msg; } } @log('Jameswain') class App { } console.log('App: ', App);
// 編譯 npx babel src/demo02.js -d dist // 執行 node src/demo02.js
爲了方便你們理解,我將babel編譯後的代碼進行了簡化,刪除了干擾邏輯
// dist/demo02.js "use strict"; function log(msg) { console.log('msg: ', msg); return function _dec (target) { console.log('target: ', target); target.msg = msg; }; } var _dec = log('Jameswain'); function App() { } _dec(App); console.log('App: ', App);
執行結果:
msg: Jameswain target: [Function: App] App: [Function: App] { msg: 'Jameswain' }
咱們平時開發中使用的react-redux
就有一個connect
裝飾器,它能夠把redux中的變量注入到指定類建立的實例中,下面咱們就經過一個例子模擬實現connect
的功能:
// src/demo03.js => 模擬實現react-redux的connect功能 // connect裝飾器 const connect = (mapStateToProps, mapDispatchToProps) => target => { const defaultState = { name: 'Jameswain', text: 'redux默認信息' }; // 模擬dispatch函數 const dispatch = payload => console.log('payload: ', payload); const { props } = target.prototype; target.prototype.props = { ...props, ...mapStateToProps(defaultState), ...mapDispatchToProps(dispatch) }; } const mapStateToProps = state => state; const mapDispatchToProps = dispatch => ({ setUser: () => dispatch({ type: 'SET_USER' }) }) @connect(mapStateToProps, mapDispatchToProps) class App { render() { console.log('渲染函數'); } } const app = new App(); console.log('app: ', app); console.log('app.props: ', app.props);
// 編譯 npx babel src/demo03.js // 執行 node dist/demo03.js
輸出結果:
app: App {} app.props: { name: 'Jameswain', text: 'redux默認信息', setUser: [Function: setUser] }
從輸出結果中能夠看到,效果跟react-redux
的connect
裝飾器同樣,返回值都被注入到App實例中的props屬性中,下面咱們來看看編譯出來的代碼長什麼樣子,老規矩爲了方便你們理解,我刪除掉babel的干擾代碼,只保留核心邏輯:
// dist/demo03.js "use strict"; // 模擬實現react-redux的connect功能 // connect裝飾器 function connect(mapStateToProps, mapDispatchToProps) { return function (target) { var defaultState = { name: 'Jameswain', text: 'redux默認信息' }; function dispatch(payload) { return console.log('payload: ', payload); }; var props = target.prototype.props; target.prototype.props = { ...props, ...mapStateToProps(defaultState), ...mapDispatchToProps(dispatch) }; }; }; function mapStateToProps(state) { return state; }; function mapDispatchToProps(dispatch) { return { setUser: function setUser() { return dispatch({ type: 'SET_USER' }); } }; }; function App() {} App.prototype.render = function() { console.log('渲染函數'); } connect(mapStateToProps, mapDispatchToProps)(App); var app = new App(); console.log('app: ', app); console.log('app.props: ', app.props);
對比編譯後的代碼,能夠發現其實裝飾器就是一個語法糖而已,實現如出一轍,只是調用的方式不同。
// 裝飾器用法 @connect(mapStateToProps, mapDispatchToProps) class App {} // 函數式用法 @connect(mapStateToProps, mapDispatchToProps)(class App {})
一個類中能夠有多個裝飾器,裝飾器的執行順序是:從下往上,從右往左執行。好比下面這個例子:
// src/demo04.js 裝飾器的執行順序 function log(target) { console.log('log: ', target); } function connect(target) { console.log('connect: ', target); } function withRouter(target) { console.log('withRouter: ', target); } @log @withRouter @connect class App { }
// 編譯 npx babel src/demo04.js -d dist // 執行 node dist/demo04.js
運行結果:
# 從下往上執行 connect: [Function: App] withRouter: [Function: App] log: [Function: App]
編譯後的代碼:
// src/demo04.js 裝飾器的執行順序 "use strict"; function log(target) { console.log('log: ', target); } function connect(target) { console.log('connect: ', target); } function withRouter(target) { console.log('withRouter: ', target); } var _class; var App = log(_class = withRouter(_class = connect(_class = function App() { }) || _class) || _class) || _class;
從編譯後的代碼中能夠看出,多個裝飾器其實就是一層層的函數嵌套,從裏往外執行,可是顯然是裝飾邏輯更清晰,易讀。