ES7 装饰器导致React Context “失效”的探究

前言

前段时间在工作中遇到使用ES7 装饰器对React Component进行封装后,导致封装后的高阶组件无法获取根组件的Context的问题。因此,对ES7 装饰器做了一些探究。


1. ES7 装饰器导致React Context “失效”

下面这两段代码渲染的结果是不一样。两段代码都很简单,在<Parent />中将familyName放入Context中,并且在<Child />进行读取。’代码2’在’代码1’上仅仅注释掉了 ES7 的装饰器

代码1:(装饰器装饰后使用React Context)
import React from 'react';
import { render } from 'react-dom';
import PropTypes from 'prop-types';

const decorator = (OldComponent) => {
  class NewComponent extends React.Component {
    render() {
      return <OldComponent />
    }
  }
  return NewComponent;
}

@decorator
class Child extends React.Component {
  render() {
    return <div>{this.context.familyName}</div>
  }
}
Child.contextTypes = {
  familyName: PropTypes.string
}

class Parent extends React.Component {
  getChildContext() {
    return { familyName: 'Zhang' }
  }
  render() {
    return <Child />
  }
}
Parent.childContextTypes = {
  familyName: PropTypes.string
}

class App extends React.Component {
  render() {
    return <Parent />
  }
}

render(<App />, document.getElementById('root'));
代码2:(不用装饰器装饰,使用React Context)
import React from 'react';
import { render } from 'react-dom';
import PropTypes from 'prop-types';

const decorator = (OldComponent) => {
  class NewComponent extends React.Component {
    render() {
      return <OldComponent />
    }
  }
  return NewComponent;
}

// @decorator
class Child extends React.Component {
  render() {
    return <div>{this.context.familyName}</div>
  }
}
Child.contextTypes = {
  familyName: PropTypes.string
}

class Parent extends React.Component {
  getChildContext() {
    return { familyName: 'Zhang' }
  }
  render() {
    return <Child />
  }
}
Parent.childContextTypes = {
  familyName: PropTypes.string
}

class App extends React.Component {
  render() {
    return <Parent />
  }
}

render(<App />, document.getElementById('root'));
代码1结果:

ES7 装饰器下的Context(Old version)

代码2结果:

无ES7 装饰器下的Context(Old version)
我们在使用ES7的装饰器对组件进行封装的时候,实际中是返回一个新的组件。在’代码1’的运行结果中,可以看出<Child />上层还有一层<NewComponent />。虽然,在’代码1’中,我们的contextType看似是添加在了<Child />上面,但实际上是添加在了<NewComponent />上面。因为,在’代码1’中,Child.contextType中的Childclass Child extends React.Component中的Child,虽然写在同一个地方,但不是同一个Child

2. 使用ES7装饰器前后的Class,虽然写在同一个地方,但是不是同一个Class
代码3: (比较一下装饰前后的class是否一致)
const As = {};
const decorator = (OldA) => {
  class NewA {
    constructor(){
      this.getData = this.getData.bind(this);
    }
  }
  As.OldA = OldA;
  As.NewA = NewA;
  return NewA;
};

@decorator
class OldA {};

console.log(`OldA === As.OldA: ${OldA === As.OldA}`)
console.log(`OldA === As.NewA: ${OldA === As.NewA}`);
代码3结果:

比较装饰器前后两个相同的Class是否为同一个Class
‘代码3’可以看出装饰器装饰完的OldA已经不等于原来的OldA
我们不妨再看看webpack编译出来的代码。(代码做了格式化的处理)

代码4:(编译前的代码)
const decorator = (OldA) => {
  class NewA {
    constructor(){
      this.getData = this.getData.bind(this);
    }
    getData() {
      console.log(this.data);
    }
  }
  NewA.data = 'NewA';
  return NewA;
};

@decorator
class OldA {};

const a = new OldA();
console.log(a);
代码5:(编译后的代码)
var _class;

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var decorator = function decorator(OldA) {
  var NewA = function () {
    function NewA() {
      _classCallCheck(this, NewA);
    }

    _createClass(NewA, [{
      key: 'getData',
      value: function getData() {
        console.log(this.data);
      }
    }]);

    return NewA;
  }();

  NewA.data = 'NewA';
  return NewA;
};

var OldA = decorator(_class = function OldA() {
  _classCallCheck(this, OldA);
}) || _class;

;

var a = new OldA();
a.getData();//# sourceURL=[module]
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vLi9jb2RlL2EwMS9pbmRleC5qcz81OWE0Il0sIm5hbWVzIjpbImRlY29yYXRvciIsIk9sZEEiLCJOZXdBIiwiY29uc29sZSIsImxvZyIsImRhdGEiLCJhIiwiZ2V0RGF0YSJdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7QUFBQSxJQUFNQSxZQUFZLFNBQVpBLFNBQVksQ0FBQ0MsSUFBRCxFQUFVO0FBQUEsTUFDcEJDLElBRG9CO0FBQUE7QUFBQTtBQUFBOztBQUFBO0FBQUE7QUFBQSxnQ0FFZDtBQUNSQyxnQkFBUUMsR0FBUixDQUFZLEtBQUtDLElBQWpCO0FBQ0Q7QUFKdUI7O0FBQUE7QUFBQTs7QUFNMUJILE9BQUtHLElBQUwsR0FBWSxNQUFaO0FBQ0EsU0FBT0gsSUFBUDtBQUNELENBUkQ7O0lBV01ELEksR0FETEQsUzs7OztBQUNZOztBQUViLElBQU1NLElBQUksSUFBSUwsSUFBSixFQUFWO0FBQ0FLLEVBQUVDLE9BQUYiLCJmaWxlIjoiLi9jb2RlL2EwMS9pbmRleC5qcy5qcyIsInNvdXJjZXNDb250ZW50IjpbImNvbnN0IGRlY29yYXRvciA9IChPbGRBKSA9PiB7XG4gIGNsYXNzIE5ld0Ege1xuICAgIGdldERhdGEoKSB7XG4gICAgICBjb25zb2xlLmxvZyh0aGlzLmRhdGEpO1xuICAgIH1cbiAgfVxuICBOZXdBLmRhdGEgPSAnTmV3QSc7XG4gIHJldHVybiBOZXdBO1xufTtcblxuQGRlY29yYXRvclxuY2xhc3MgT2xkQSB7fTtcblxuY29uc3QgYSA9IG5ldyBPbGRBKCk7XG5hLmdldERhdGEoKTtcbiJdLCJzb3VyY2VSb290IjoiIn0=
//# sourceURL=webpack-internal:///./code/a01/index.js

‘代码4’就是用装饰器修饰了OldA,并在后面直接new OldA。’代码5’是webpack编译后的结果。其中,可以看出,在new OldA之前,OldA是作为decorator的返回值。而decorator函数返回了内部的NewA,而原来的OldA是作为默认参数传递到decorator里面。

因此,这里就印证了我们对’代码1’的解释。contextType没有添加到装饰器修饰前的<Child />,自然在<Child />中没能获取到相应的context

3. 使用新的react context api可以避免这个问题,或者在装饰器内部给旧组件添加ContextType
代码6:(使用新的react context api配合装饰器)
import React from 'react';
import { render } from 'react-dom';

const FamilyContext = React.createContext('Default');

const decorator = (OldComponent) => {
  class NewComponent extends React.Component {
    render() {
      return <OldComponent />
    }
  }
  return NewComponent;
}

@decorator
class Child extends React.Component {
  render() {
    return <FamilyContext.Consumer>
      {familyName => <div>{familyName}</div>}
    </FamilyContext.Consumer>
  }
}

class Parent extends React.Component {
  render() {
    return <FamilyContext.Provider value='Zhang'>
      <Child />
    </FamilyContext.Provider>
  }
}

class App extends React.Component {
  render() {
    return <Parent />
  }
}

render(<App />, document.getElementById('root'));
代码6结果:

ES7 装饰器下的Context(New version)

代码7:(在装饰器内部给旧组件添加ContextType)
import React from 'react';
import { render } from 'react-dom';
import PropTypes from 'prop-types';

const decorator = (OldComponent) => {
  OldComponent.contextTypes = {
    familyName: PropTypes.string
  }
  class NewComponent extends React.Component {
    render() {
      return <OldComponent />
    }
  }
  return NewComponent;
}

@decorator
class Child extends React.Component {
  render() {
    return <div>{this.context.familyName}</div>
  }
}

class Parent extends React.Component {
  getChildContext() {
    return { familyName: 'Zhang' }
  }
  render() {
    return <Child />
  }
}
Parent.childContextTypes = {
  familyName: PropTypes.string
}

class App extends React.Component {
  render() {
    return <Parent />
  }
}

render(<App />, document.getElementById('root'));
代码7结果:

在装饰器内部给旧组件添加ContextType


结论

1. ES7 装饰器修饰类的前后,表示类的同一个变量并不相等。装饰器修饰后的变量是装饰器的返回值,在某些情况下容易混。(即:下面两个A不是同一个A)
...
@decorator
class A{}

console.log(A);
2. 使用React Context 老版本API 配合装饰器的时候需要将contextType写在装饰器内部
3. 使用React Context 新版本API 不会遇到这个问题,因为它不用在类上绑定contextType
原创内容,转载请注明,谢谢)
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值