前言
在最近做的一个 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# 玩多了, 更喜欢这样的玩法, 而对高阶组件这种方式总是感觉很奇怪)
本篇文章算是自己的一次重构笔记吧, 写的只是个人的一点理解, 如果有更好的办法或者疏漏的地方欢迎批评指正.
来源: https://www.cnblogs.com/vvjiang/p/9283006.html