关于javascript:JavaScript-装饰器-Decorator

30次阅读

共计 4402 个字符,预计需要花费 12 分钟才能阅读完成。

环境搭建

因为装璜器属于一个在提案中的语法,所以不论是 node 还是浏览器,当初都没有间接反对这个语法,咱们要想应用该语法,就必须要通过 babel 将它进行一个编译转换,所以咱们须要搭建一个 babel 编译环境。

1、装置 babel 相干包

npm i @babel/cli @babel/core @babel/plugin-proposal-decorators @babel/preset-env -D

2、在我的项目根目录下创立.babelrc

{
  "presets": ["@babel/preset-env"],
  "plugins": [
    [
      "@babel/plugin-proposal-decorators",
      {"legacy": true}
    ]
  ]
}

根底环境搭建好当前,接下来咱们就能够纵情的应用装璜器了

类装璜器

类装璜器,顾名思义就是用来装璜整个类的,能够用来批改类的一些行为。

简略类装璜器

// src/demo01.js
// 类装璜器的简略利用
function log(target) {console.log('target:', target);
}

@log
class App {}

编译,执行

 // 应用 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 加了一下额定的逻辑,删掉这些逻辑后,装璜器转换后的代码其实是上面这样子的:

function log(target) {console.log('target:', target);
}

class App {};

log(App);

执行输入:

target:  [Function: App]

能够看到其实类装璜器就是一个函数,承受一个类作为参数,装璜器函数外部的 target 参数就是被装璜的类自身,咱们能够在装璜器函数外部对这个类进行一些批改,比方:增加动态属性,给原型增加函数等等。

带参数的类装璜器

带参数的装璜器,须要在里面再套一层承受参数的函数,像上面这样:

// 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 实现

咱们平时开发中应用的 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-reduxconnect装璜器一样,返回值都被注入到 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;

从编译后的代码中能够看出,多个装璜器其实就是一层层的函数嵌套,从里往外执行,然而显然是装璜逻辑更清晰,易读。

正文完
 0