zoukankan      html  css  js  c++  java
  • React中嵌套组件与被嵌套组件的通信

    前言

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

  • 相关阅读:
    Spring MVC
    Spring
    MyBatis
    Java Listener
    Java Filter
    JSTL
    EL
    pyltp安装
    美团面经-java开发
    oppo面经-java开发
  • 原文地址:https://www.cnblogs.com/vvjiang/p/9293741.html
Copyright © 2011-2022 走看看