React中嵌套组件与被嵌套组件的通讯

前言

在React项目的开发中常常会遇到这样一个场景:嵌套组件与被嵌套组件的通讯。node

好比Tab组件啊,或者下拉框组件。react

场景

这里应用一个最简单的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为使用它们的组件。ide

在上述场景中,点击哪一个TabItem项时,就将这个TabItem项激活。函数

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

需求的变动与缺陷的暴露

在上述场景下应用上述方案是没有问题的,可是咱们一般用的Tab没有这么简单,好比当点击武汉这个TabItem时,武汉地区的美食也要展现出来。this

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

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组件为:code

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来解决这个问题。

相关文章
相关标签/搜索