React 组件 ES2015 Class 改写细节
createClass
工厂函数转换为 ES2015 Class
ES5:
var MyComponent = React.createClass({
// ...
});
ES2015:
class MyComponent extends React.Component {
// ...
}
提取 propTypes
和 getDefaultTypes
与 createClass
对象字面量不同,ES2015 极简 Class 只允许定义方法,不能定义属性(有可能在 ES7 中扩展),诸如 propTypes
等属性都要提取为 Class 本身的属性(静态属性)。
静态属性指的是 Class 本身的属性,即
Class.propname
,而不是定义在实例对象(this
)上的属性。
ES5:
var MyComponent = React.createClass({
propTypes: {
someProp: React.PropTypes.string
},
getDefaultProps: function() {
return {
someProp: ''
};
}
});
ES2015:
class MyComponent extends React.Component {
}
MyComponent.propTypes = {
someProp: React.PropTypes.string,
}
MyComponent.defaultProps = {
someProp: '',
}
ES2015 Class 静态属性语法:
class MyComponent extends React.Component {
static propTypes = {
someProp: React.PropTypes.string,
};
static defaultProps = {
someProp: '',
};
}
显然,使用 static
更加方便代码块的组织,后面涉及的 contextTypes
等属性也可以按此语法编写。
注意:
使用
static
语法需要在 Babel 配置中添加stage-0
。{ "presets": ["stage-0"] }
转移 getInitialState
ES5:
var MyComponent = React.createClass({
getInitialState: function() {
return {
someState: this.props.someProp
};
}
});
ES2015:
class MyComponent extends React.Component {
constructor(props) {
super(props);
// 初始状态设置转移到 constructor 里了
this.state = {
someState: props.someProp,
};
}
}
注意:
ES5 的继承,实质是先创造子类的实例对象
this
,然后再将父类的方法添加到this
上面(Parent.apply(this)
)。ES6
的继承机制完全不同,实质是先创造父类的实例对象this
(所以必须先调用super
方法),然后再用子类的构造函数修改this
。如果子类没有定义
constructor
方法,这个方法会被默认添加,代码如下。也就是说,不管有没有显式定义,任何一个子类都有constructor
方法。constructor(...args) { super(...args); }
另一个需要注意的地方是,在子类的构造函数中,只有调用
super
之后,才可以使用this
关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有super
方法才能返回父类实例。
(via)
constructor
必须先调用 super
方法,再进行初始状态设置、this
绑定等操作。
提取 Context 相关属性(方法)
ES5:
var Foo = React.createClass({
contextTypes: {
tag: React.PropTypes.string,
className: React.PropTypes.string
},
getInitialState: function() {
return {
tag: this.context.tag,
className: this.context.className
};
},
render: function() {
var Tag = this.state.tag;
return <Tag className={this.state.className} />;
}
});
var Outer = React.createClass({
childContextTypes: {
tag: React.PropTypes.string,
className: React.PropTypes.string
},
getChildContext: function() {
return {
tag: 'span',
className: 'foo'
};
},
render: function() {
return <Foo />;
}
});
ES2015:
class Foo extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
tag: context.tag,
className: this.context.className,
};
}
render() {
var Tag = this.state.tag;
return <Tag className={this.state.className} />;
}
}
Foo.contextTypes = {
tag: React.PropTypes.string,
className: React.PropTypes.string,
};
class Outer extends React.Component {
getChildContext() {
return {
tag: 'span',
className: 'foo',
};
}
render() {
return <Foo />;
}
}
Outer.childContextTypes = {
tag: React.PropTypes.string,
className: React.PropTypes.string,
};
以上代码来自 React 官方测试用例。
绑定 this
转换为 ES2015 Class 以后,实例方法不会自动绑定 this
,需要手动绑定或者使用箭头函数。
ES5:
var MyComponent = React.createClass({
handleClick: function() {
console.log(this);
},
render: function() {
return (
<div
onClick={this.handleClick}
>
Hello world.
</div>
);
}
});
ES2015:
class MyComponent extends React.Component {
handleClick() {
console.log(this);
}
render() {
// 使用 bind 绑定
return (
<div onClick={this.handleClick.bind(this)}>
Hello world.
</div>
);
// 或者使用箭头函数 <div onClick={() => this.handleClick()}>
}
}
官方推荐在 constructor
中绑定,方便在不同的地方调用:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log(this);
}
render() {
return (
<div onClick={this.handleClick}>
Hello world.
</div>
);
}
}
如果方法很多,还可以抽象出一个专门一个基类。
每个方法手动绑定:
class MyComponent extends React.Component {
constructor() {
super();
this.handleClick = this.handleClick.bind(this);
this.handleFoo = this.handleFoo.bind(this);
}
// ...
}
使用基类绑定:
class BaseComponent extends React.Component {
_bind(...methods) {
methods.forEach((method) => {
this[method] = this[method].bind(this);
});
}
}
class MyComponent extends BaseComponent {
constructor() {
super();
this._bind('handleClick', 'handleFoo');
}
// ...
}
Mixin 问题
由于 ES2015 的原因,使用 ES2015 Class 创建的 React 组件不支持 mixin。这应该是已有项目使用 ES2015 Class 重构的最大障碍。编写 Amaze UI Touch 时,虽然使用了部分 ES2015 语法,但基于使用 mixin 考虑,仍然使用 createClass
定义组件。在一些项目中,也能看到 Class 语法和工厂方法混用,原因皆在于 mixin。
修饰符可能是将来的解决方案,但仍需等待,目前可先参考以下内容:
- React Mixins when using ES6 and React
- Mixins Are Dead. Long Live Composition
- react-mixin: mixins in react with es6 style classes
参考链接
- Reusable Components - ES6 Classes
- React Blog - Plain JavaScript Classes
- Refactoring React Components to ES6 Classes
- Writing React Components with ES6
- 初探 ES6(4)極簡的 Classes
- Building The Facebook News Feed With Relay
- isMounted is an Antipattern
- React Components, Elements, and Instances
- React on ES6+
- React and ES6