由于装饰器属于一个在提案中的语法,因此无论是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;
从编译后的代码中能够看出,多个装饰器其实就是一层层的函数嵌套,从里往外执行,可是显然是装饰逻辑更清晰,易读。