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 相关文章推荐
window.location.reload()方法刷新页面弹出要再次显示该网页对话框
Apr 24 Javascript
js父窗口关闭时子窗口随之关闭完美解决方案
Apr 29 Javascript
超级好用的jQuery圆角插件 Corner速成
Aug 31 Javascript
jQuery插件MixItUp实现动画过滤和排序
Apr 12 Javascript
jQuery仿gmail实现fixed布局的方法
May 27 Javascript
简介JavaScript中的push()方法的使用
Jun 09 Javascript
JavaScript forEach()遍历函数使用及介绍
Jul 08 Javascript
Highcharts使用简例及异步动态读取数据
Dec 30 Javascript
js不间断滚动的简单实现
Jun 03 Javascript
JavaScript 中有关数组对象的方法(详解)
Aug 15 Javascript
switch语句的妙用(必看篇)
Oct 03 Javascript
关于AngularJS中几种Providers的区别总结
May 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加MYSQL服务器
2006/10/09 PHP
phpize的深入理解
2013/06/03 PHP
php打包网站并在线压缩为zip
2016/02/13 PHP
PHP 配置后台登录以及模板引入
2017/01/24 PHP
javascript document.execCommand() 常用解析
2009/12/14 Javascript
jQuery 学习6 操纵元素显示效果的函数
2010/02/07 Javascript
JQuery 技巧和窍门整理(8个)
2010/04/22 Javascript
读jQuery之三(构建选择器)
2011/06/11 Javascript
JS更改select内option属性的方法
2015/10/14 Javascript
JavaScript程序设计之JS调试
2015/12/09 Javascript
jquery判断checkbox是否选中及改变checkbox状态的实现方法
2016/05/26 Javascript
JS实现仿百度文库评分功能
2017/01/12 Javascript
Javascript 使用ajax与C#获取文件大小实例详解
2017/01/13 Javascript
vue-router实现组件间的跳转(参数传递)
2017/11/07 Javascript
微信小程序模板和模块化用法实例分析
2017/11/28 Javascript
nodejs 使用nodejs-websocket模块实现点对点实时通讯
2018/11/28 NodeJs
微信公众号平台接口开发 获取access_token过程解析
2019/08/14 Javascript
node.js事件轮询机制原理知识点
2019/12/22 Javascript
vue实现计算器功能
2020/02/22 Javascript
vue tab切换,解决echartst图表宽度只有100px的问题
2020/07/19 Javascript
vue 动态组件(component :is) 和 dom元素限制(is)用法说明
2020/09/04 Javascript
Vue router传递参数并解决刷新页面参数丢失问题
2020/12/02 Vue.js
[01:43]3.19DOTA2发布会 三代刀塔人第三代
2014/03/25 DOTA
[55:42]VG vs VGJ.T 2018国际邀请赛淘汰赛BO1 8.21
2018/08/22 DOTA
Django框架文件上传与自定义图片上传路径、上传文件名操作分析
2019/05/10 Python
浅谈Python类中的self到底是干啥的
2019/11/11 Python
pytorch 指定gpu训练与多gpu并行训练示例
2019/12/31 Python
python序列类型种类详解
2020/02/26 Python
python实现飞船大战
2020/04/24 Python
Python如何读写CSV文件
2020/08/13 Python
HTML5的Geolocation地理位置定位API使用教程
2016/05/12 HTML / CSS
KIKO MILANO荷兰网上商店:意大利专业化妆品品牌
2017/05/12 全球购物
对标管理实施方案
2014/03/12 职场文书
优秀团员事迹材料1500字
2014/08/31 职场文书
社区党务工作总结2015
2015/05/19 职场文书
2015年教师节新闻稿
2015/07/17 职场文书