React-mixins在多个组件中复用代码

React使用组合而不是继承来处理父子组件,这也是React的特点之一。组件的复合只是React提供的用于定制和特殊化组件的方式之一。React的mixins提供了一种途径,帮助我们定义可以在多组件之间共用的方法。

Mixins will allow you to apply behaviors to multiple React components.


先直观的感受下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var Timer = React.createClass({
getInitialState:function(){
return {secondsElapsed:0}
},
tick:function(){
this.setState({secondsElapsed:this.state.secondsElapsed+1})
},
componentDidMount:function(){
this.interval = setInterval(this.tick,1000)
},
componentWillUnmount:function(){
clearInterval(this.interval)
},
render:function(){
return (
<div>Seconds Elapsed:{this.state.secondsElapsed}</div>
)
}
})
ReactDOM.render(<Timer />,document.getElementById('example'))

调用的顺序是:"getInitialState" ``"componentDidMount" ``"tick"。是一个简单的定时器组件。

当我们用了mixin,不过如果我们有很多的定时器,执行的都是上面的这段代码,难道我们要写很多吗?任何一个语言里面都有复用的方法,任何一个框架也是。连sass,less也都有 :) 。下面是react-mixins的用法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var IntervalMixin = function(interval){
return {
componentDidMount:function(){
this._interval = setInterval(this.tick,1000)
},
componentWillUnmount:function(){
clearInterval(this._interval)
}
}
}
var Timer = React.createClass({
mixins:[IntervalMixin(1000)],
getInitialState:function(){
return {secondsElapsed:0}
},
tick:function(){
this.setState({secondsElapsed:this.state.secondsElapsed+1})
},
render:function(){
return (
<div>Seconds Elapsed:{this.state.secondsElapsed}</div>
)
}
})
ReactDOM.render(<Timer />,document.getElementById('example'))

这么看来mixins是不是是相当于把一部分功能提取出来公用?更通俗的解释:就是类似于merge,A 有3个方法 compoment有2个方法,mixin[A]以后,就有5个方法。并且调用没有任何问题。


几个需要注意的地方

在mixin中写的生命周期相关的回调都会被合并,也就是他们都会执行,而不会互相覆盖掉。比如

1
2
3
4
5
6
React.createClass({
mixins:[{
getInitialState:function(){return {a:1}}
}],
getInitialState:function(){return {b:2}}
})

这样下来,最后得到的初始state{a:1,b:2},如果mixin中的方法和组件类中的方法返回的对象中存在重复的键,React会抛出一个错误来警示。

再比如:你在mixin中可以定义 componentDidMount来初始化组件,他不会覆盖掉使用这个mixin的组件。实际执行的时候,会先执行mixincomponentDidMount,最后执行组件的 componentDidMount方法。

因为mixin的作用是抽离公共功能,不存在渲染dom的需要,所以它没有render方法。如果你定义了render方法,那么他会和组件的render方法冲突而报错。

同样,mixin不应该污染state,所以他也没有 setState 方法。mixin应该只提供接口(即方法),不应该提供任何属性。mixin内部的属性最好是通过闭包的形式作为私有变量存在。

1
2
3
4
var Timer = function() {  
var test = 1;//私有属性
return {test + 1}
}

除了生命周期方法可以重复以外,其他的方法都不可以重复,否则会报错。比如有一个Amixin,有一个Bmixin,并且AmixinBmixin中都有方法find(),当他们同时被引入到一个component时,就会报错。再比如Aminx里面有,引入它的component也有find()方法。那么也会报错。除非他们不是find()的方法,而是声明周期的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var Amixin = {
find: function () { //... }
};

var Bmixin = {
find: function () { //... }
};

//第一种情况
var Component = React.createClass({
mixins: [Amixin,Bmixin],
render: function () {
return (
//...
)
}
});
//或者第二种情况
var Component = React.createClass({
mixins: [Amixin],
find:function(){ //... }
render: function () {
return (
//...
)
}
});

这两种情况都报错。那么如果这个时候我们确实用的是生命周期里面的方法,那么在第一种情况里面,声明周期方法调用顺序又是怎么样的?

Mixins数组引入的顺序,决定了Mixins里生命周期方法的执行顺序。并且都在当前组件里面的该方法前面调用。


另一个例子

下面这个例子记录了从2014/1/1到现在的总秒数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var IntervalMixin = {
setInterval:function(callback,interval){
var token = setInterval(callback,interrval)
this._intervals.push(token)
return token
},
componentDidMount:function(){
this._intervals = []
},
componentWillUnmount:function(){
this._interval.map(clearInterval)
}
}
var Timer = React.createClass({
mixins:[IntervalMixin],
componentDidMount:function(){
this.setInterval(this.forceUpdate.bind(this),1000)
},
render:function(){
var from = Number(new Date(2014,0,1))
var to = Date.now()
return (
<div>{Math.round(to-from)/1000}</div>
)
}
})
ReactDOM.render(<Timer />,document.getElementById('example'))


总结

还是很好理解的。mixins大大的解决了代码重复的强大的工具。也减少了我们的阅读量。同时允许我们在不污染组件本身的情况下做一些丑陋的处理。当然也应该取思考,什么时候用mixin比较好。不要盲目的用。