简要介绍:分离UI与业务的方法一直在演进,从早期的mixins,到HOC,再到Render Prop,本文主要对比HOC,谈谈Render Props
1 . 早期的mixins
早期复用业务通过mixins来实现,比如组件A和组件B中,有一些公用函数,通过mixins剥离这些公用部分,并将其组合成一个公用集合,然后将这个集合传递给每个组件调用。
//公有的函数部分
const someMixins={
printColor(){
console.log(this.state.color);
}
printWeight(){
console.log(this.state.weight);
}
}
class Apple extends React.Component{
//仅仅作为演示,mixins一般是通过React.createClass创建,并且ES6也没有key:value这种写法
mixins:[someMixins]
constructor(props){
super(props);
this.state={
color:'red',
weight:'100g'
}
this.printColor=this.printColor.bind(this);
}
render(){
return <div className="m-box" onClick={this.printColor}>
这是一个苹果
div>
}
}
class Banana extends React.Component{
mixins:[someMixins]
constructor(props){
super(props);
this.state={
color:'yellow',
weight:'200g'
}
this.printColor=this.printColor.bind(this);
}
render(){
return <div className="m-box" onClick={this.printColor}>
这是一个香蕉
div>
}
}
上述的例子中,Apple和Banana都具有printColor和printWeight方法,通过mixins分离公共业务。mixins已经在React16.0版本移除,这里仅仅做一个介绍。
2 . HOC
HOC简单理解就是组件工厂,接受原始组件作为参数,添加完功能与业务后,返回新的组件。下面来介绍HOC参数的几个例子。
(1)参数仅为原始组件,比如:
const redApple = withFruit(Apple);
(2)参数为原始组件和一个对象,比如:
const redApple = withFruit(Apple,{color:'red',weight:'200g'});
但是这种情况比较少用,如果对象中仅仅传递的是属性,其实完全可以通过组件的props实现值的传递,我们用HOC的主要目的是分离业务,关于UI的展示,以及一些组件中的属性和状态,我们一般通过props来指定比较方便
(3)参数为原始组件和一个函数,比如:
const redApp=withFruit(App,()=>{console.log('I am a fruit')})
这种是HOC的典型例子,原始组件参数表示UI部分,函数参数表示处理逻辑,在HOC工厂中进行耦合后生成新的组件。
(4)柯里化
最常见的是仅以一个原始组件作为参数,但是在外层包裹了业务逻辑,比如react-redux的conect函数中:
class Admin extends React.Component{
}
const mapStateToProps=(state)=>{
return {
};
}
const mapDispatchToProps=(dispatch)=>{
return {
}
}
const connect(mapStateToProps,mapDispatchToProps)(Admin)
这里不是严格的柯里化,但是思想是一样的,在HOC的工厂函数中在包一层父函数,用于指定业务逻辑。
3 . HOC的缺点
下面我们来看看HOC的缺点:
(1)难以溯源,且存在属性覆盖问题
如果原始组件A,先后通过工厂函数1,工厂函数2,工厂函数3….构造,最后生成了组件B,我们知道组件B中有很多与A组件不同的props,但是我们仅仅通过组件B,并不能知道哪个组件来自于哪个工厂函数。同时,如果有2个工厂函数同时修改了组件A的某个同名属性,那么会有属性覆盖的问题,会使得前一个工厂函数的修改结果失效。
(2)HOC是静态构建的
所谓静态构建,也就是说生成的是一个新的组件,并不会马上render,HOC组件工厂是静态构建一个组件,这类似于重新声明一个组件的部分。也就是说,HOC工厂函数里面的声明周期函数,也只有在新组件被渲染的时候才会执行。
(3)会产生无用的空组件
3 . render props
class Cat extends React.Component {
render() {
const mouse = this.props.mouse;
return (
"/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
);
}
}
class Mouse extends React.Component {
constructor(props) {
super(props);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.state = { x: 0, y: 0 };
}
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
{/*
Instead of providing a static representation of what renders,
use the `render` prop to dynamically determine what to render.
*/ }
{this.props.render(this.state)}
div>
);
}
}
class MouseTracker extends React.Component {
render() {
return (
<div>
Move the mouse around!
(
)}/>
div>
);
}
}
上述是官网给出的例子,我们来看主要是部分是下面这两句:
Class Mouse extends React.component{
...
{this.props.render(this.state)}
...
}
......
(
)}/>
在使用Mouse组件的时候,通过一个render属性,传递一个可用组件Cat给父组件Mouse,而在Mouse组件中,可以将本身的state对象传递给Cat组件,Cat组件中的mouse属性的值与Mouse父组件中的state相同。
精简来说: 就是父组件可以将自己的state传递给子组件,而子组件可以根据父组件的state对象,来进行render。
这样做的好处是:
(1)不用担心props的命名问题
(2)可以溯源,子组件的props一定是来自于直接父组件
(3)是动态构建的。