前言
前段时间在工作中遇到使用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结果:
代码2结果:
我们在使用ES7的装饰器对组件进行封装的时候,实际中是返回一个新的组件。在’代码1’的运行结果中,可以看出<Child />
上层还有一层<NewComponent />
。虽然,在’代码1’中,我们的contextType看似是添加在了<Child />
上面,但实际上是添加在了<NewComponent />
上面。因为,在’代码1’中,Child.contextType
中的Child
和class 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结果:
‘代码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结果:
代码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结果:
结论
1. ES7 装饰器修饰类的前后,表示类的同一个变量并不相等。装饰器修饰后的变量是装饰器的返回值,在某些情况下容易混。(即:下面两个A不是同一个A)
...
@decorator
class A{}
console.log(A);