• React组件重构:嵌套+继承 与 高阶组件


    前言

    在最近做的一个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#玩多了,更喜欢这样的玩法,而对高阶组件这种方式总是感觉很奇怪)

    本篇文章算是自己的一次重构笔记吧,写的只是个人的一点理解,如果有更好的办法或者疏漏的地方欢迎批评指正。

  • 相关阅读:
    Luogu P5072 [Ynoi2015]盼君勿忘
    activemq的高级特性:通配符式分层订阅
    activemq的高级特性:消息的可靠性
    activemq的高级特性:消息持久订阅
    activemq的高级特性:消息存储持久化
    activeMQ的高级特性:嵌入activemq
    activeMQ的request-response请求响应模式
    activeMQ的spring、springboot的DEMO
    activemq的搭建、启动,简单demo
    mysql中的锁
  • 原文地址:https://www.cnblogs.com/vvjiang/p/9283006.html
Copyright © 2020-2023  润新知