React组件重构之嵌套+继承及高阶组件详解


Posted in Javascript onJuly 19, 2018

前言

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

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
JavaScript 事件参考手册
Dec 24 Javascript
使用JQuery和s3captche实现一个水果名字的验证
Aug 14 Javascript
jquery 入门教程 [翻译] 推荐
Aug 17 Javascript
javascript实现yield的方法
Nov 06 Javascript
javascript:void(0)是什么意思及href=#与href=javascriptvoid(0)的区别
Nov 13 Javascript
ExtJs的Ext.Ajax.request实现waitMsg等待提示效果
Jun 14 Javascript
JQuery和html+css实现带小圆点和左右按钮的轮播图实例
Jul 22 jQuery
checkbox:click事件触发span元素内容改变的方法
Sep 11 Javascript
vue.js根据代码运行环境选择baseurl的方法
Feb 28 Javascript
vue项目中,main.js,App.vue,index.html的调用方法
Sep 20 Javascript
如何使用electron-builder及electron-updater给项目配置自动更新
Dec 24 Javascript
使用JavaScript和MQTT开发物联网应用示例解析
Aug 07 Javascript
微信小程序实现折叠展开效果
Jul 19 #Javascript
详解Angularjs 自定义指令中的数据绑定
Jul 19 #Javascript
微信小程序实现天气预报功能
Jul 18 #Javascript
vue代理和跨域问题的解决
Jul 18 #Javascript
小程序自定义组件实现城市选择功能
Jul 18 #Javascript
微信小程序实践之动态控制组件的显示/隐藏功能
Jul 18 #Javascript
微信小程序项目实践之主页tab选项实现
Jul 18 #Javascript
You might like
require(),include(),require_once()和include_once()区别
2008/03/27 PHP
PHP中source #N问题的解决方法
2014/01/27 PHP
php查询mysql大量数据造成内存不足的解决方法
2015/03/04 PHP
Symfony2安装的方法(2种方法)
2016/02/04 PHP
为javascript添加String.Format方法
2020/08/11 Javascript
让JavaScript拥有类似Lambda表达式编程能力的方法
2010/09/12 Javascript
javascript判断机器是否联网的2种方法
2013/08/09 Javascript
浅谈利用JavaScript进行的DDoS攻击原理与防御
2015/06/04 Javascript
JavaScript实现横向滑出的多级菜单效果
2015/10/09 Javascript
关于JS中match() 和 exec() 返回值和属性的测试
2016/03/21 Javascript
jquery Ajax 全局调用封装实例详解
2017/01/16 Javascript
bootstrap日期控件问题(双日期、清空等问题解决)
2017/04/19 Javascript
JS身份证信息验证正则表达式
2017/06/12 Javascript
解决element-ui中下拉菜单子选项click事件不触发的问题
2018/08/22 Javascript
抖音上用记事本编写爱心小程序教程
2019/04/17 Javascript
localstorage实现带过期时间的缓存功能
2019/06/28 Javascript
微信小程序模板消息推送的两种实现方式
2019/08/27 Javascript
在Webpack中用url-loader处理图片和字体的问题
2020/04/28 Javascript
python itchat实现微信自动回复的示例代码
2017/08/14 Python
Python数字图像处理之霍夫线变换实现详解
2018/01/12 Python
Python2 Selenium元素定位的实现(8种)
2019/02/25 Python
Python时间序列缺失值的处理方法(日期缺失填充)
2019/08/11 Python
详解用Python调用百度地图正/逆地理编码API
2020/07/02 Python
Python实现打包成库供别的模块调用
2020/07/13 Python
解决Django响应JsonResponse返回json格式数据报错问题
2020/08/09 Python
Python 找出英文单词列表(list)中最长单词链
2020/12/14 Python
关于探究python中sys.argv时遇到的问题详解
2021/02/23 Python
HTML5所有标签汇总及标签意义解释
2015/03/12 HTML / CSS
美国背景检查、公共记录和人物搜索网站:BeenVerified
2018/02/25 全球购物
工商管理实习自我鉴定
2013/09/28 职场文书
高中三年学习生活的自我评价
2013/10/10 职场文书
学校师德承诺书
2014/05/23 职场文书
连锁超市项目计划书
2014/09/15 职场文书
反腐倡廉观后感
2015/06/08 职场文书
写给汽车4S店的创业计划书,拿来即用!
2019/08/09 职场文书
python中的random模块和相关函数详解
2022/04/22 Python