关于javascript:JavaScript-装饰器-Decorator

环境搭建

因为装璜器属于一个在提案中的语法,所以不论是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;

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

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理