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 indexOf函数使用说明
Jul 03 Javascript
解决jQuery动态获取手机屏幕高和宽的问题
May 07 Javascript
浅谈Javascript中匀速运动的停止条件
Dec 19 Javascript
js代码实现点击按钮出现60秒倒计时
Jan 28 Javascript
JS常用知识点整理
Jan 21 Javascript
Angular.js与node.js项目里用cookie校验账户登录详解
Feb 22 Javascript
通过npm引用的vue组件使用详解
Mar 02 Javascript
node 使用 async 控制并发的方法
May 07 Javascript
使用vue-infinite-scroll实现无限滚动效果
Jun 22 Javascript
ES6 Array常用扩展的应用实例分析
Jun 26 Javascript
Vue数据双向绑定底层实现原理
Nov 22 Javascript
解决vue项目打包上服务器显示404错误,本地没出错的问题
Nov 03 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
双料怀旧--SHARP GF515的维护、修理和简单调试
2021/03/02 无线电
php print EOF实现方法
2009/05/21 PHP
黑夜路人出的几道php笔试题
2009/08/04 PHP
实现在同一方法中获取当前方法中新赋值的session值解决方法
2014/06/26 PHP
smarty内置函数section的用法
2015/01/22 PHP
jquery下组织javascript代码(js函数化)
2010/08/25 Javascript
基于JQuery的浮动DIV显示提示信息并自动隐藏
2011/02/11 Javascript
向左滚动文字 js代码效果
2013/08/17 Javascript
通过$(this)使用jQuery包装后的方法或属性
2014/05/18 Javascript
javascript从作用域链谈闭包
2020/07/29 Javascript
jQuery插件实现带圆点的焦点图片轮播切换
2016/01/18 Javascript
常用js,css文件统一加载方法(推荐) 并在加载之后调用回调函数
2016/09/23 Javascript
Angular中使用ui router实现系统权限控制及开发遇到问题
2016/09/23 Javascript
angular ngClick阻止冒泡使用默认行为的方法
2016/11/03 Javascript
JavaScript实现瀑布流图片效果
2017/06/30 Javascript
webpack构建换肤功能的思路详解
2017/11/27 Javascript
详解Angular-ui-BootStrap组件的解释以及使用
2018/07/13 Javascript
解决在vue项目中webpack打包后字体不生效的问题
2018/09/01 Javascript
Angular请求防抖处理第一次请求失效问题
2019/05/17 Javascript
Vue中keep-alive的两种应用方式
2020/07/15 Javascript
解决ant Design Search无法输入内容的问题
2020/10/29 Javascript
微信小程序对图片进行canvas压缩的方法示例详解
2020/11/12 Javascript
python实现指定字符串补全空格的方法
2015/04/30 Python
Python有序字典简单实现方法示例
2017/09/28 Python
python中WSGI是什么,Python应用WSGI详解
2017/11/24 Python
python3使用scrapy生成csv文件代码示例
2017/12/28 Python
Python中常见的异常总结
2018/02/20 Python
IronPython连接MySQL的方法步骤
2019/12/27 Python
高清屏下canvas重置尺寸引发的问题的解决
2019/10/14 HTML / CSS
捐款倡议书范文
2014/02/02 职场文书
食品安全宣传标语
2014/06/07 职场文书
倡导文明标语
2014/06/16 职场文书
三八节活动简报
2015/07/20 职场文书
2016年员工年度考核评语
2015/12/02 职场文书
Python中的min及返回最小值索引的操作
2021/05/10 Python
react中useState使用:如何实现在当前表格直接更改数据
2022/08/05 Javascript