在React组件构建的过程中,常常会有一类功能需要不同的组件公用的情况。此时就涉及到了抽象的问题。
mixin
所谓Mixin,就是讲一个模块混入到另一个模块之中,或是一个类中。
事实上mixin就类似于多重继承。
封装mixin方法
function mixin(obj, mixins){
const newObj = obj;
newObj.prototype = Object.create(obj.prototype);
for(let prop in mixins){
if(mixins.hasOwnProperty(prop)){
newObj.prototype[prop] = mixins[prop];
}
}
return newObj;
}
const bigMixin = {
fly: ()=>{console.log('I can fly');}
}
const big = function(){
console.log('new big');
}
const flyBig = mixin(big, bigMixin);
对于广义的mixin方法,就是用赋值的方式将mixin对象里的方法都挂载到原对象上。
在React中使用mixin
React在使用createClass
构建组件是提供了mixin
属性,比如官方封装的PureRenderMixin
:
import React from 'react';
import PureRenderMixin from 'react-addons-pure-render-mixin';
React.createClass({
mixins: [PureRenderMixin]
render(){
return <div>foo</div>
}
});
在createClass
对象参数中传入数组mixins
,里面封装了我们所需要的模块。mixins
数组也可以增加多个mixin
,其每一个mixin
方法之间若有重合,对于普通方法和生命周期方法是有所区分的。
在不同的mixin里实现两个名字一样的普通方法,按理说,后面的方法应该会覆盖前面的方法,但是在React中是不会覆盖的,而是会报一个ReactClassInterface
的错误,指出你尝试在组件中多次定义一个方法。
可以看出,React中不允许出现重名普通方法的mixin。
而如果是React的生命周期方法,则会将各个磨矿的生命周期方法叠加在一起顺序执行。
mixin的问题
- 破坏了原有组件的封装
mixin通过混入方法,为原有组件带来新的特性,但它也可能带来了新的state
和props
,这意味着组件有一些“不可见”的状态需要我们去维护,但我们在使用的时候并不清楚。
另外,mixin也有可能去依赖其他的mixin,这样会建立一个mixin的依赖链。
命名冲突
不同的mixin中的命名在不可知的情况下重用是不可控的。
尽管可以通过更改名字来解决,但遇到第三方引用,或已经引用了几个mixin的情况下,总是要花一定的成本去解决冲突。
增加复杂性
引入一个mixin,就给组件引进了该mixin可能包含的生命周期方法,那么引入越多mixin,就有越多生命周期方法被引入组件,这样一来,组件的代码实现可能就复杂到难以维护。
高阶组件
可能我们更熟悉“高阶函数”这个概念,例如常见的map
、reduce
、sort
都是高阶函数。它们接受函数作为输入,或者是用函数作为输出。
那么同样的,高阶组件接受React组件作为输入,输出一个新的React组件。
实现高阶组件的方法有如下两种:
- 属性代理
- 反向继承
属性代理
属性代理是最常见的高阶组件的实现方法,例如:
const MyContainer = (WrappedComponent) => {
return class extends Component{
render(){
const newProps = {
text: 'this is new Props'
}
return <WrappedComponent {...this.props} {...newProps}/>;
}
}
}
class Controlled extends Component{
constructor(props){
super(props);
console.log(props.text);
this.changeHandle = this.changeHandle.bind(this);
}
changeHandle(e){
emitter.emit('change', e.target.value);
}
render(){
const {inputState} = this.props;
return (
<div>
<input type='text' value={inputState} onChange={this.changeHandle}></input>
</div>
)
}
}
const Test = MyContainer(Controlled);
此时Test
就是一个React组件,在控制台中也能看到为Controlled
组件添加的text prop
。
反向继承
const MyContainer = (WrappedComponent) => {
return class extends WrappedComponent{
render(){
return super.render();
}
}
}
通过反向继承实现的高阶组件都是继承自WrappedComponent
,因为继承了WrappedComponent
所有的调用的会反向。