前言
在最近做的一個react項目中,遇到了一個比較典型的需要重構的場景:提取兩個組件中共同的部分。
最開始通過使用嵌套組件和繼承的方式完成了這次重構。
但是后來又用高階組件重新寫了一遍,發現更好一點。
在這里記錄下這兩種方式以便之后參考和演進。
本次重構的場景
因為場景涉及到具體的業務,所以我現在將它簡化為一個簡單的場景。
現在有兩個黑色箱子,箱子上都有一個紅色按鈕,A箱子充滿氣體,按了按鈕之后箱子里面氣體變紅,B箱子充滿泥土,按了之后箱子里面泥土變紅。
那么現在上一個簡單的重構前代碼:
BoxA.jsx
import React, { Component, PropTypes } from 'react'
class BoxA extends Component {
state={
color:'black'
}
handleClick=()=>{
this.setState({
color:'red'
})
}
handleShake=()=>{
/* 搖動后氣體沒聲音 */
}
render() {
return (
/* 這里面當然沒有onShake這種事件,理解意思就行了 */
<div style={{backgroundColor:'black'}} onShake={this.handleShake}>
<button onClick={this.handleClick} style={{backgroundColor:'red'}}></button>
<div>
/* 氣體組件,沒毛病 */
<氣體 color={this.state.color} />
</div>
</div>
)
}
}
BoxB.jsx
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
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
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
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
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
import React, { Component, PropTypes } from 'react'
class BasicBox extends Component {
state={
color:'black'
}
handleClick=()=>{
this.setState({
color:'red'
})
}
}
BoxA.jsx
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
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
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
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
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)。
總結
以上的兩種方式中,高階組件的方式對於后來者在修改上更友好一點。
但是用嵌套+繼承的方式理解起來其實更容易一點,特別是去重構一個復雜的組件時,通過這種方式往往更快,拆分起來更容易。(我個人更傾向於這種,不知道是不是C#玩多了,更喜歡這樣的玩法,而對高階組件這種方式總是感覺很奇怪)
本篇文章算是自己的一次重構筆記吧,寫的只是個人的一點理解,如果有更好的辦法或者疏漏的地方歡迎批評指正。