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

  • 相关阅读:
    LeetCode 842. Split Array into Fibonacci Sequence
    LeetCode 1087. Brace Expansion
    LeetCode 1219. Path with Maximum Gold
    LeetCode 1079. Letter Tile Possibilities
    LeetCode 1049. Last Stone Weight II
    LeetCode 1046. Last Stone Weight
    LeetCode 1139. Largest 1-Bordered Square
    LeetCode 764. Largest Plus Sign
    LeetCode 1105. Filling Bookcase Shelves
    LeetCode 1027. Longest Arithmetic Sequence
  • 原文地址:https://www.cnblogs.com/vvjiang/p/9293741.html
Copyright © 2020-2023  润新知