React中嵌套组件与被嵌套组件的通信过程


Posted in Javascript onJuly 11, 2018

前言

在React项目的开发中经常会遇到这样一个场景:嵌套组件与被嵌套组件的通信。
比如Tab组件啊,或者下拉框组件。

场景

这里应用一个最简单的Tab组件来呈现这个场景。

import React, { Component, PropTypes } from 'react'
class Tab extends Component {
 static propTypes = {
  children: PropTypes.node
 }
 render() {
  return (
   <ul>
    {this.props.children}
   </ul>
  )
 }
}
class TabItem extends Component {
 static propTypes = {
  name: PropTypes.string,
  active: PropTypes.bool,
  onClick: PropTypes.func
 }
 handleClick = () => {
  this.props.onClick(this.props.name)
 }
 render() {
  return (
   <li onClick={this.handleClick} className={this.props.active ? 'active' : 'noActive'}>
    {this.props.name}
   </li>
  )
 }
}
export default class Area extends Component {
 state = {
  activeName: ''
 }
 handleClick = (name) => {
  this.setState({
   activeName: name
  })
 }
 render() {
  return (
   <Tab>
    {['武汉', '上海', '北京'].map((item) => <TabItem onClick={this.handleClick} active={this.state.activeName === item} name={item} />)}
   </Tab>
  )
 }
}

这里有Tab,TabItem和Area三个组件,其中Tab为嵌套组件,TabItem为被嵌套组件,Area为使用它们的组件。
在上述场景中,点击哪个TabItem项时,就将这个TabItem项激活。

以上方案算是嵌套组件最常用的方案了。

需求的变更与缺陷的暴露

在上述场景下应用上述方案是没有问题的,但是我们通常用的Tab没有这么简单,比如当点击武汉这个TabItem时,武汉地区的美食也要展示出来。

这种场景下就需要修改TabItem组件为:

class TabItem extends Component {
 static propTypes = {
  name: PropTypes.string,
  active: PropTypes.bool,
  onClick: PropTypes.func,
  children: PropTypes.node
 }

 handleClick = () => {
  this.props.onClick(this.props.name)
 }

 render() {
  return (
   <li onClick={this.handleClick} className={this.props.active ? 'active' : 'noActive'}>
    <span className='switchBtn'>{this.props.name}</span>
    <div className={this.props.active ? 'show' : 'hide'}>
     {this.props.children}
    </div>
   </li>
  )
 }
}

然后沿用上述方案,那么就需要改变Area组件为:

export default class Area extends Component {
 state = {
  activeName: ''
 }

 handleClick = (name) => {
  this.setState({
   activeName: name
  })
 }

 render() {
  return (
   <Tab>
    <TabItem onClick={this.handleClick} active={this.state.activeName === '武汉'} name={'武汉'} >
     武汉的美食,这里有一大堆jsx代码
    </TabItem>
    <TabItem onClick={this.handleClick} active={this.state.activeName === '上海'} name={'上海'} >
     武汉的美食,这里有一大堆jsx代码
    </TabItem>
    <TabItem onClick={this.handleClick} active={this.state.activeName === '北京'} name={'北京'} >
     武汉的美食,这里有一大堆jsx代码
    </TabItem>
   </Tab>
  )
 }
}

这里的Area使用TabItem的时候已经没办法用 数组+map 的形式去写了。

因为这里有大量的jsx在这里,如果那样去写,代码的可读性将会非常糟糕。

那么用上面的写法写的时候,就会出现一个问题,就是onClick在不断重复,active的判断也在不断重复。

尝试掩盖active判断重复的问题

这个比较容易,修改代码如下:

class TabItem extends Component {
 static propTypes = {
  name: PropTypes.string,
  activeName: PropTypes.string,
  onClick: PropTypes.func,
  children: PropTypes.node
 }

 handleClick = () => {
  this.props.onClick(this.props.name)
 }

 render() {
  return (
   <li onClick={this.handleClick} className={this.props.activeName === this.props.name ? 'active' : 'noActive'}>
    <span className='switchBtn'>{this.props.name}</span>
    <div className={this.props.active ? 'show' : 'hide'}>
     {this.props.children}
    </div>
   </li>
  )
 }
}

export default class Area extends Component {
 state = {
  activeName: ''
 }

 handleClick = (name) => {
  this.setState({
   activeName: name
  })
 }

 render() {
  return (
   <Tab>
    <TabItem onClick={this.handleClick} activeName={this.state.activeName} name={'武汉'} >
     武汉的美食,这里有一大堆jsx代码
    </TabItem>
    <TabItem onClick={this.handleClick} activeName={this.state.activeName} name={'上海'} >
     武汉的美食,这里有一大堆jsx代码
    </TabItem>
    <TabItem onClick={this.handleClick} activeName={this.state.activeName} name={'北京'} >
     武汉的美食,这里有一大堆jsx代码
    </TabItem>
   </Tab>
  )
 }
}

尝试掩盖onClick不断重复的问题

想要onClick不重复,那么就不能将其写在TabItem上,而是应该写在Tab上。

那么这个地方就得用到事件冒泡的机制。

将onClick写在Tab上,然后根据捕获的事件消息,获取target的class是否为switchBtn,然后得到target的text。
再将这个text赋值为activeName。

并且你还得期望点击的switchBtn的内的结构不那么复杂,最好是就只有一个文本。

如果需求还要给Tab项的切换按钮每个都加上图标,那么你还得看这个事件的target是不是这个图标。那么又需要做更多的处理了。

想一想就觉得麻烦。

一般在这种情况下,脑子里唯一的想法就是,就这样吧,这个onClick重复就重复吧,没什么大不了的。
连我自己都懒得写这部分代码了。

嵌套组件与被嵌套组件的通信:React.Children与React.cloneElement

实际上要解决上面的问题,只需要一个东西就好了,那就是嵌套组件能传递值给被嵌套组件的props,比如onClick。

那么先上一份代码吧。

class TabItem extends Component {
 static propTypes = {
  name: PropTypes.string,
  activeName: PropTypes.string,
  onClick: PropTypes.func,
  children: PropTypes.node
 }
 handleClick = () => {
  this.props.onClick(this.props.name)
 }
 render() {
  return (
   <li onClick={this.handleClick} className={this.props.activeName === this.props.name ? 'active' : 'noActive'}>
    <span className='switchBtn'>{this.props.name}</span>
    <div className={this.props.active ? 'show' : 'hide'}>
     {this.props.children}
    </div>
   </li>
  )
 }
}
class Tab extends Component {
 static propTypes = {
  children: PropTypes.node,
  onClickItem: PropTypes.func,
  activeName: PropTypes.string
 }
 render() {
  return (
   <ul>
    {
     React.Children.map(this.props.children,(child)=>{
      if (child.type === TabItem) {
       return React.cloneElement(child, {
        // 把父组件的props.name赋值给每个子组件(父组件传值给子组件)
        activeName: this.props.activeName,
        // 父组件的方法挂载到props.onClick上,以便子组件内部通过props调用
        onClick: this.props.onClickItem
       })
      } else {
       return child
      }
     })
    }
   </ul>
  )
 }
}
export default class Area extends Component {
 state = {
  activeName: ''
 }

 handleClick = (name) => {
  this.setState({
   activeName: name
  })
 }
 render() {
  return (
   <Tab activeName={this.state.activeName} onClick={this.handleClick} >
    <TabItem name={'武汉'} >
     武汉的美食,这里有一大堆jsx代码
    </TabItem>
    <TabItem name={'上海'} >
     武汉的美食,这里有一大堆jsx代码
    </TabItem>
    <TabItem name={'北京'} >
     武汉的美食,这里有一大堆jsx代码
    </TabItem>
   </Tab>
  )
 }
}

通过这种方式,我们发现在使用Tab和TabItem时会变得非常简单。

那么接下来让我们介绍一下解决嵌套组件通信这个问题的关键:React.Children.map和React.cloneElement。
React.Children

React.Children是专门用来处理this.props.children这个东西的工具。

通常props.children可以是任何变量类型:数组、对象、文本或者其他的一些类型,但是我们这里使用

React.Children.map(this.props.children,(child)=>{
 // ***
})

无论this.props.children的类型是什么都不会报错。

这里只是用了React.children的map函数,实际上它还有foreach,count以及only的玩法。

foreach就不解释了,很容易理解是干嘛的。
count就是得到被嵌套组件的数量。
only就是返回被嵌套的组件,并且只能有一个被嵌套的组件,否则会抛异常。

React.cloneElement

先看下面这段代码

const child= <Child value={1} />
const newChild=React.cloneElement(child,{
 name:'额外的props'
},'123')

newChild的值为:

<Child value={1} name='额外的props' >
 123
</Child>

可以很明显看到,React.cloneElement的就相当克隆一个组件,然后可以传给它额外的props和children。

总结

对于简单的嵌套组件用最开始的方法其实已经够了。

但是对于复杂的嵌套组件为了更好更方便的使用,往往需要与被嵌套的组件进行通信。

而我们可以使用React.Children和React.cloneElement来解决这个问题。

Javascript 相关文章推荐
比较详细的关于javascript中void(0)的具体含义解释
Aug 02 Javascript
Ext JS 4实现带week(星期)的日期选择控件(实战二)
Aug 21 Javascript
jquery购物车实时结算特效实现思路
Sep 23 Javascript
JavaScript将页面表格导出为Excel的具体实现
Dec 27 Javascript
jQuery中prev()方法用法实例
Jan 08 Javascript
director.js实现前端路由使用实例
Feb 03 Javascript
深入理解JavaScript系列(22):S.O.L.I.D五大原则之依赖倒置原则DIP详解
Mar 05 Javascript
JS+Ajax实现百度智能搜索框
Aug 04 Javascript
AngularJS中filter的使用实例详解
Aug 25 Javascript
Vue实现内部组件轮播切换效果的示例代码
Apr 07 Javascript
vue点击自增和求和的实例代码
Nov 06 Javascript
基于Element封装一个表格组件tableList的使用方法
Jun 29 Javascript
JSON数据中存在单个转义字符“\”的处理方法
Jul 11 #Javascript
JS实现动态生成html table表格的方法分析
Jul 11 #Javascript
vue监听键盘事件的快捷方法【推荐】
Jul 11 #Javascript
vue移动端实现红包雨效果
Jun 23 #Javascript
vue实现学生录入系统之添加删除功能
Jul 11 #Javascript
微信小程序实现红包雨功能
Jul 11 #Javascript
小程序ios音频播放没声音问题的解决
Jul 11 #Javascript
You might like
thinkPHP的Html模板标签使用方法
2012/11/13 PHP
PHP中对各种加密算法、Hash算法的速度测试对比代码
2014/07/08 PHP
两种php去除二维数组的重复项方法
2015/11/04 PHP
PHP记录页面停留时间的方法
2016/03/30 PHP
PHP版微信第三方实现一键登录及获取用户信息的方法
2016/10/14 PHP
PHP实现的策略模式简单示例
2017/08/25 PHP
js静态作用域的功能。
2006/12/25 Javascript
JavaScript 中的replace方法说明
2007/04/13 Javascript
Prototype 学习 工具函数学习($A方法)
2009/07/12 Javascript
js中settimeout方法加参数
2014/02/28 Javascript
单击某一段文字改写文本颜色
2014/06/06 Javascript
jQuery控制DIV层实现由大到小,由远及近动画变化效果
2015/10/09 Javascript
jQuery基础的工厂函数以及定时器的经典实例分析
2016/05/20 Javascript
BootStrap创建响应式导航条实例代码
2016/05/31 Javascript
微信小程序 数据绑定详解及实例
2016/10/25 Javascript
jQuery实现对象转为url参数的方法
2017/01/11 Javascript
浅谈jQuery中事情的动态绑定
2017/02/12 Javascript
Vue封装Swiper实现图片轮播效果
2018/02/06 Javascript
详解swiper在vue中的应用(以3.0为例)
2018/09/20 Javascript
JS中数据结构与算法---排序算法(Sort Algorithm)实例详解
2019/06/17 Javascript
Vue 设置axios请求格式为form-data的操作步骤
2019/10/29 Javascript
解决vue项目本地启动时无法携带cookie的问题
2021/02/06 Vue.js
Python和php通信乱码问题解决方法
2014/04/15 Python
浅谈Python的Django框架中的缓存控制
2015/07/24 Python
简单谈谈python中的多进程
2016/11/06 Python
利用python将图片转换成excel文档格式
2017/12/30 Python
python保存文件方法小结
2018/07/27 Python
python 穷举指定长度的密码例子
2020/04/02 Python
HTML5中indexedDB 数据库的使用实例
2017/05/11 HTML / CSS
Lands’ End官网:经典的美国生活方式品牌
2016/08/14 全球购物
丝芙兰香港官网:Sephora香港
2018/03/13 全球购物
String和StringBuffer的区别
2015/08/13 面试题
一些高难度的SQL面试题
2016/11/29 面试题
PHP使用Redis队列执行定时任务实例讲解
2021/03/24 PHP
利群广告词
2014/03/20 职场文书
常用的MongoDB查询语句的示例代码
2021/07/25 MongoDB