什么是装饰器

装饰器(Decorator)函数,是用来修改类的行为,一个装饰器函数有三个参数

  • target 装饰的目标对象
  • name 类的成员,属性或者方法
  • descriptor 成员描述,传递给Object.defineProperty的描述

如何使用装饰器

目前浏览器还不支持装饰器,所以需要babel来进行转换,先全局安装 @babel/core@babel/cli

npm i @babel/core @babel/cli -g

然后再局部安装 @babel/plugin-proposal-class-properties@babel/plugin-proposal-decorators

npm i @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators -D

.babelrc配置babel插件

{
  "plugins": [
    ["@babel/plugin-proposal-decorators", { "legacy": true }],
    ["@babel/plugin-proposal-class-properties", { "loose" : true }]
  ]
}

声明装饰器

我们都知道装饰器其实就是一个函数,所以声明一个装饰器很简单

// 声明一个只读的装饰器函数,通过descriptor给name改为只读
function readOnly(target, name, descriptor) {
  descriptor.writable = false;
  return descriptor
}

一个完整的栗子 

// 声明一个只读的装饰器函数,通过descriptor给name改为只读
function readOnly(target, name, descriptor) {
  descriptor.writable = false;
  return descriptor
}
// 函数弃用
function deprecate(target, name, descriptor) {
  return {
    ...descriptor,
    value: function depreactionWrapper() {
      console.warn('该函数将被移除');
      return descriptor.value.apply(this, arguments)
    }
  }
}
class Person {
  @readOnly
  name = 'hello'

  @readOnly
  @deprecate
  say() {
    console.log(`my name is ${ this.name }`)
  }
}

const p = new Person()
p.hello = 'fuck'        // hello
p.say()                 // 该函数将被移除 my name is hello

通过命令行 babel index.js -o bundle.js 可以把 @decoratorFunction 语法转为es5就可以在浏览器执行,转换后的代码如下

var _class, _descriptor, _temp;

function _initializerDefineProperty(target, property, descriptor, context) {
  if (!descriptor) return;
  Object.defineProperty(target, property, {
    enumerable: descriptor.enumerable,
    configurable: descriptor.configurable,
    writable: descriptor.writable,
    value: descriptor.initializer ? descriptor.initializer.call(context) : void 0
  });
}

function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) {
  var desc = {};
  Object['ke' + 'ys'](descriptor).forEach(function (key) {
    desc[key] = descriptor[key];
  });
  desc.enumerable = !!desc.enumerable;
  desc.configurable = !!desc.configurable;
  if ('value' in desc || desc.initializer) {
    desc.writable = true;
  }
  desc = decorators.slice().reverse().reduce(function (desc, decorator) {
    return decorator(target, property, desc) || desc;
  }, desc);
  if (context && desc.initializer !== void 0) {
    desc.value = desc.initializer ? desc.initializer.call(context) : void 0;
    desc.initializer = undefined;
  }
  if (desc.initializer === void 0) {
    Object['define' + 'Property'](target, property, desc);
    desc = null;
  }
  return desc;
}

function readOnly(target, name, descriptor) {
  descriptor.writable = false;
  return descriptor;
}

function deprecate(target, name, descriptor) {
  return {
    ...descriptor,
    value: function depreactionWrapper() {
      console.warn('该函数将被移除');
      return descriptor.value.apply(this, arguments);
    }
  };
}

let Person = (_class = (_temp = class Person {
  constructor() {
    _initializerDefineProperty(this, "name", _descriptor, this);
  }

  say() {
    console.log(`my name is ${this.name}`);
  }

}, _temp), (_descriptor = _applyDecoratedDescriptor(_class.prototype, "name", [readOnly], {
  configurable: true,
  enumerable: true,
  writable: true,
  initializer: function () {
    return 'hello';
  }
}), _applyDecoratedDescriptor(_class.prototype, "say", [readOnly, deprecate], Object.getOwnPropertyDescriptor(_class.prototype, "say"), _class.prototype)), _class);
const p = new Person();
p.say();

可以看出装饰器就是通过 Object.defineProperty 在类的原型上的新增或者修改属性或者方法

装饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,装饰器能在编译阶段运行代码。也就是说,装饰器本质就是编译时执行的函数。

装饰器在react的使用

在使用react-redux的时候我们会用connect的方法把store和当前组件关联,我们都知道connect是一个高阶函数

import { connect } from 'react-redux';
class Post extends PureComponent{}
export default connect(mapStateToProps, mapDispatchToProps)(Post)

使用装饰器来写,就会变成下面这样

import { connect } from 'react-redux';
// connect store to component
@connect(mapStateToProps, mapDispatchToProps)
class Post extends PureComponent{}
export default Post