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 相关文章推荐
js 获取class的元素的方法 以及创建方法getElementsByClassName
Mar 11 Javascript
jquery重新播放css动画所遇问题解决
Aug 21 Javascript
js获取字符串最后一位方法汇总
Nov 13 Javascript
基于JavaScript如何实现ajax调用后台定义的方法
Dec 29 Javascript
javascript动态获取登录时间和在线时长
Feb 25 Javascript
jQuery Ajax 实例代码 ($.ajax、$.post、$.get)
Apr 29 Javascript
JS制作类似选项卡切换的年历
Dec 03 Javascript
vue基于Vue2.0和高德地图的地图组件实例
Apr 28 Javascript
js微信分享实现代码
Oct 11 Javascript
如何在vue里添加好看的lottie动画
Aug 02 Javascript
详解Nuxt.js 实战集锦
Nov 19 Javascript
javascript实现简易的计算器
Jan 17 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
php 数组排序 array_multisort与uasort的区别
2011/03/24 PHP
Yii中CGridView关联表搜索排序方法实例详解
2014/12/03 PHP
php判断一个数组是否为有序的方法
2015/03/27 PHP
CodeIgniter针对lighttpd服务器URL重写的方法
2015/06/10 PHP
thinkPHP5.0框架开发规范简介
2017/03/25 PHP
ThinkPHP实现图片上传操作的方法详解
2017/05/08 PHP
tp5.1 框架路由操作-URL生成实例分析
2020/05/26 PHP
javascript里的条件判断
2007/02/27 Javascript
JavaScript 模仿vbs中的 DateAdd() 函数的代码
2007/08/13 Javascript
jquery.combobox中文api和例子,修复了上面的小bug
2011/03/28 Javascript
使用jQuery Ajax功能时需要注意的一个问题(内存溢出)
2012/05/30 Javascript
Javascript跨域请求的4种解决方式
2013/03/17 Javascript
Jquery带搜索框的下拉菜单
2013/05/06 Javascript
javascript动态向网页中添加表格实现代码
2014/02/19 Javascript
Js获取下拉框选定项的值和文本的实现代码
2014/02/26 Javascript
实例讲解JavaScript的Backbone.js框架中的View视图
2016/05/05 Javascript
javascript鼠标跟随运动3种效果(眼球效果,苹果菜单,方向跟随)
2016/10/27 Javascript
JS根据生日月份和日期计算星座的简单实现方法
2016/11/24 Javascript
Bootstrap基本插件学习笔记之折叠(22)
2016/12/08 Javascript
Js中将Long转换成日期格式的实现方法
2018/06/05 Javascript
详解JavaScript 中 if / if...else...替换方式
2018/07/15 Javascript
python网络编程学习笔记(八):XML生成与解析(DOM、ElementTree)
2014/06/09 Python
python链接Oracle数据库的方法
2015/06/28 Python
python中装饰器级连的使用方法示例
2017/09/29 Python
python输入错误密码用户锁定实现方法
2017/11/27 Python
python Web开发你要理解的WSGI &amp; uwsgi详解
2018/08/01 Python
Scrapy框架爬取西刺代理网免费高匿代理的实现代码
2019/02/22 Python
python-OpenCV 实现将数组转换成灰度图和彩图
2020/01/09 Python
Python坐标轴操作及设置代码实例
2020/06/04 Python
CSS3的transition和animation的用法实例介绍
2014/08/20 HTML / CSS
销售职业生涯规划范文
2014/03/14 职场文书
护士求职自荐信范文
2014/03/19 职场文书
2014年中秋寄语
2014/08/11 职场文书
纪委立案决定书
2015/06/24 职场文书
2015年物流客服工作总结
2015/07/27 职场文书
幼儿园教师教学反思
2016/03/02 职场文书