React组件重构:嵌套+继承 与 高阶组件
2018-07-09 11:336047029
前言
在最近做的一个react项目中,遇到了一个比较典型的需要重构的场景:提取两个组件中共同的部分。
最开始通过使用嵌套组件和继承的方式完成了这次重构。
但是后来又用高阶组件重新写了一遍,发现更好一点。
在这里记录下这两种方式以便之后参考和演进。
本次重构的场景
因为场景涉及到具体的业务,所以我现在将它简化为一个简单的场景。
现在有两个黑色箱子,箱子上都有一个红色按钮,A箱子充满气体,按了按钮之后箱子里面气体变红,B箱子充满泥土,按了之后箱子里面泥土变红。
那么现在上一个简单的重构前代码:
BoxA.jsx
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 28 29 30
| import React, { Component, PropTypes } from 'react'
class BoxA extends Component { state={ color:'black' }
handleClick=()=>{ this.setState({ color:'red' }) }
handleShake=()=>{ }
render() { return ( <div style={{backgroundColor:'black'}} onShake={this.handleShake}> <button onClick={this.handleClick} style={{backgroundColor:'red'}}> </button> <div> <气体 color={this.state.color} /> </div> </div> ) } }
|
BoxB.jsx
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
| import React, { Component, PropTypes } from 'react'
class BoxB extends Component { state={ color:'black' } handleClick=()=>{ this.setState({ color:'red' }) }
handleShake=()=>{ }
render() { return ( <div style={{backgroundColor:'black'}} onShake={this.handleShake}> <button onClick={this.handleClick} style={{backgroundColor:'red'}}></button> <div> <泥土 color={this.state.color} /> </div> </div> ) } }
|
使用嵌套组件进行重构
看看上面的代码,即使在业务简化的情况下都有很多重复的,所以得重构。
对于这种很明显的箱子类问题,一般都会采用嵌套组件的方式重构。
Box.jsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import React, { Component, PropTypes } from 'react'
class Box extends Component {
static propTypes = { children: PropTypes.node, onClick: PropTypes.func, onShake: PropTypes.func }
render() { return ( <div style={{backgroundColor:'black'}} onShake={this.props.onShake}> <button onClick={this.props.onClick} style={{backgroundColor:'red'}}></button> <div> {this.children} </div> </div> ) } }
|
BoxA.jsx
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
| import React, { Component, PropTypes } from 'react' import Box from './Box.jsx'
class BoxA extends Component { state={ color:'black' }
handleClick=()=>{ this.setState({ color:'red' }) }
handleShake=()=>{ }
render() { return ( <Box onClick={this.handleClick} onShake={this.props.handleShake}> <气体 color={this.state.color} /> </Box> ) } }
|
BoxB.jsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import React, { Component, PropTypes } from 'react'
class BoxB extends Component { state={ color:'black' } handleClick=()=>{ this.setState({ color:'red' }) }
handleShake=()=>{ }
render() { return ( <Box onClick={this.handleClick} onShake={this.props.handleShake}> <泥土 color={this.state.color} /> </Box> ) } }
|
使用继承组件的方式进行重构
对于很多场景而言,使用了嵌套组件后,可能就不需要或者没法进一步进行组件提炼了。
然而完成这波操作后,我们发现嵌套组件BoxA和BoxB依然存在重复代码,即按下按钮变红这部分代码。
这部分代码可以使用嵌套组件与被嵌套组件的通信机制来处理,技术上而言依然可以将这部分代码用嵌套组件的方式来解决。
但是为了保证组件的单一职责,即箱子就是个带红色按钮可以摇动的箱子,我们不知道里面以后会放什么进去,就不能说不管以后里面放什么,只要我一按红色按钮,里面的物质都会变红。
这部分代码肯定是不能放在嵌套组件Box里,因为它直接操作着被嵌套的内容。
那么在这里我们可以使用继承组件的方式。
Box.jsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import React, { Component, PropTypes } from 'react'
class Box extends Component { static propTypes = { children: PropTypes.node, onClick: PropTypes.func, onShake: PropTypes.func }
render() { return ( <div style={{backgroundColor:'black'}} onShake={this.props.onShake}> <button onClick={this.props.onClick} style={{backgroundColor:'red'}}></button> <div> {this.children} </div> </div> ) } }
|
BasicBox.jsx
1 2 3 4 5 6 7 8 9 10 11 12
| import React, { Component, PropTypes } from 'react' class BasicBox extends Component { state={ color:'black' }
handleClick=()=>{ this.setState({ color:'red' }) } }
|
BoxA.jsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import React, { Component, PropTypes } from 'react' import Box from './Box.jsx'
class BoxA extends BasicBox { handleShake=()=>{ }
render() { return ( <Box onClick={this.handleClick} onShake={this.props.handleShake}> <气体 color={this.state.color} /> </Box> ) } }
|
BoxB.jsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import React, { Component, PropTypes } from 'react'
class BoxB extends BasicBox { handleShake=()=>{ }
render() { return ( <Box onClick={this.handleClick} onShake={this.props.handleShake}> <泥土 color={this.state.color} /> </Box> ) } }
|
通过修改后的代码,就可以将BoxA和BoxB中相同的部分提取到BasicBox中。
这样我们相当于将一个功能块提取了出来,你可以继承BasicBox(这个命名可能不好,容易引起混淆),如果不使用state的值也完全没有任何问题。
但是这样做也许会带了一些别的问题。
我们自己去看这段代码的时候其实不难理解,不过之后让其他人对这块代码做修改时,后来的人就会感到奇怪,BoxA
中突然间使用了一个不知道从哪里来的handleClick
。
使用高阶组件进行重构
为了解决上面的问题,后来又使用高阶组件的方式玩了一遍:
hocBox.jsx
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 28 29 30
| import React, { Component, PropTypes } from 'react'
hocBox=(WrappedComponent)=>{ return class Box extends Component{ static propTypes = { onShake: PropTypes.func }
state={ color:'black' }
handleClick=()=>{ this.setState({ color:'red' }) }
render() { return ( <div style={{backgroundColor:'black'}} onShake={this.props.handleShake}> <button onClick={this.handleClick} style={{backgroundColor:'red'}}></button> <div> <WrappedComponent color={this.state.color} /> </div> </div> ) } } }
|
BoxA.jsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import React, { Component, PropTypes } from 'react' import Box from './hocBox.jsx'
const 气体WithBtnBox=hocBox(气体) class BoxA extends BasicBox { handleShake=()=>{ }
render() { return ( <气体WithBtnBox onShake={this.handleShake} /> ) } }
|
BoxB.jsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import React, { Component, PropTypes } from 'react' import Box from './hocBox.jsx'
const 泥土WithBtnBox=hocBox(泥土) class BoxA extends BasicBox { handleShake=()=>{ }
render() { return ( <泥土WithBtnBox onShake={this.handleShake} /> ) } }
|
高阶组件的使用就像设计模式中的装饰者模式(Decorator Pattern)。
总结
以上的两种方式中,高阶组件的方式对于后来者在修改上更友好一点。
但是用嵌套+继承的方式理解起来其实更容易一点,特别是去重构一个复杂的组件时,通过这种方式往往更快,拆分起来更容易。