10. React组件间的通信

3. React组件间的通信

3.1. 认识组件嵌套
复制代码
###### 1. 组件之间存在嵌套关系:

* 1.1. 在之前的案例中,只是创建了一个组件App;
* 1.2. 如果我们一个应用程序将所有的逻辑都放在一个组件中, 那么这个组件就会变得非常的臃肿和难以维护;
* 1.3. 所以组件化的核心思想应该是对组件进行拆分,拆分成一个个小的组件;
* 1.4. 在将这些组件组合嵌套在一起,最终形成应用程序;
复制代码
###### 2. 上面的嵌套逻辑如下,它们存在如下关系:

* 2.1. App组件是Header、Main、Footer组件的父组件;
* 2.2. Main组件是Banner、ProductList组件的父组件
* 2.3. 如下图  
  ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/06282683604140cf9c55f7b7e66a3e60.png)
复制代码
###### 3. 提高开发效率的vscode插件
    1. 原因:每次开发时都需要引入组件,都需要引入相关的依赖,很麻烦。
    1. 使用插件:ES7+ React/Redux/React-Native snippets, 这个插件会包含很多的代码片段还挺好用的,所以使用了这个插件的代码片段,
3.2. 认识组件嵌套
复制代码
###### 3.2.1 在开发过程中,我们会经常遇到需要组件之间相互通信:

*
  1. App可能使用了多个Header, 每个地方的Header展示的内容不同,那么我们就需要使用者传递给Header一些数据,让其进行展示
*
  2. 又比如在Main中一次性请求了Banner数据和ProductList数据,那么就需要传递给他们来进行展示;
*
  3. 也可能是子组件中发生了事件,需要又父组件来完成某些操作,那就需要子组件向父组件传递事件;
复制代码
###### 3.2.2 总之,在一个React项目中,组件之间通信是非常重要的环节;
复制代码
###### 3.2.3 父组件在展示子组件,可能会传递一些数据给子组件;

*
  1. 父组件通过`属性=值`的形式来传递给子组件数据;
*
  2. 父组件通过`props参数获取父组件传递过来的数据`;
*
  3. 示例代码如下:

  * 父组件:

  ```js
    import React, { Component } from 'react'
    import MainBanner from './MainBanner'
    import MainProductList from './MainProductList'

    export class Main extends Component {
      constructor () {
        super()

        this.state = {
          title: '商品轮播图',
          banners: ['新歌单','新MV', '新歌曲'],
          productList: ['推荐商品', '热门商品', '流行商品']
        }
      }
      render() {
        const { title, banners, productList} = this.state
        return (
          <div className='main'>
            <div>main</div>
            <MainBanner banners={banners} title={title} />
            <MainProductList productList={productList} />
          </div>
        )
      }
    }

    export default Main
  ```

  * 子组件

  ```js
  import React, { Component } from 'react'

  export class MainBanner extends Component {
    constructor(props) {
      // 把props传递给super,内部会自动保存执行这个操作this.props = props
      // 如果不写constructor,默认就会执行super(props);自动保存this.props = props
      super(props)
      this.state = {
        name: 'MainBanner'
      }
    }
    render() {
      const { title, banners} = this.props
      console.log('props==', this.props)

      return (
        <div>
          <h2>{title}</h2>
          <ul>
            {
              banners.map(item => {
                return (
                  <li key={item}>{item}</li>
                )
              })
            }
          </ul>
        </div>
      )
    }
  }

  export default MainBanner
  ```
3.3. 参数propsTypes
复制代码
###### 3.1. 对于传递给子组件的数据,有时候我们可能希望进行验证,特别是大型项目来说:

*
  1. 当然,如果你项目中默认`继承了Flow或者TypeScript`,那么直接就`可以进行类型验证`;
*
  2. 但是,即使我们没有使用Flow或者TypeScript,也可以`通过props-types库来进行参数验证`;
复制代码
###### 3.2. 从React v15.5开始,React.PropTypes已移入另一个包中:prop-types库;
复制代码
###### 3.3. 更多的验证方式,可以参考官网:[官网地址](https://reactjs.org/docs/typechecking-with-proptypes.html)

* 1.比如验证数组,并且数组中包含哪些元素;

* 2.比如验证对象,并且对象中`包含哪些key以及value是什么类型`;

* 3.比如某个原生是必须的,使用`requiredFunc: PropTypes.func.isRequired`;

  * 设置必选和指定类型

  ```js
    import React, { Component } from 'react'
    import PropTypes from 'prop-types';

    export class MainBanner extends Component {
      constructor(props) {
        // 把props传递给super,内部会自动保存执行这个操作this.props = props
        // 如果不写constructor,默认就会执行super(props);自动保存this.props = props
        super(props)
        this.state = {
          name: 'MainBanner'
        }
      }
      render() {
        const { title, banners} = this.props

        return (
          <div>
            <h2>{title}</h2>
            <ul>
              {
                banners.map((item) => {
                  return (
                    <li key={item.acm}>
                      {item.title}
                    </li>
                  )
                })
              }
            </ul>
          </div>
        )
      }
    }

    // MainBanner.propTypes = {
    //   banners: PropTypes.array.isRequired,
    //   title: PropTypes.string.isRequired,
    // };

    MainBanner.propTypes = {
      banners: PropTypes.array.isRequired,
      title: PropTypes.string.isRequired
    };

    export default MainBanner
  ```

  * 不传值或者传值类型为其他类型,会报错

  ```jsx
    import React, { Component } from 'react'
    // import MainBanner from './MainBanner'
    import MyComponent from './MainBanner'
    import MainProductList from './MainProductList'
    import axios from 'axios'

    export class Main extends Component {
      constructor () {
        super()

        this.state = {
          title: '商品轮播图',
          banners: [],
          productList: []
        }
      }
      componentDidMount () {
        axios.get('http://123.207.32.32:8000/home/multidata').then(res => {
          console.log(res);
          const data = res.data.data
          const banners = data.banner.list
          const recommend = data.recommend.list
          this.setState({
            banners,
            productList: recommend
          })
          
        })
      }
      
      render() {
        const {  title, banners, productList} = this.state
        // 
        return (
          <div className='main'>
            <div>main</div>
            <MyComponent banners={banners} title={title}/>
            <MyComponent />
            <MainProductList productList={productList} />
          </div>
        )
      }
    }

    export default Main
  ```

  * 报错图如下:  
    ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/707f81a3e6d14bee861b28f56703db7c.png)

  ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/abf03b0a174446b9baa44885d758f805.png)
  * 正确传参:

  ```js
    import React, { Component } from 'react'
  // import MainBanner from './MainBanner'
  import MyComponent from './MainBanner'
  import MainProductList from './MainProductList'
  import axios from 'axios'

  export class Main extends Component {
    constructor () {
      super()

      this.state = {
        title: '商品轮播图',
        banners: [],
        productList: []
      }
    }
    componentDidMount () {
      axios.get('http://123.207.32.32:8000/home/multidata').then(res => {
        console.log(res);
        const data = res.data.data
        const banners = data.banner.list
        const recommend = data.recommend.list
        this.setState({
          banners,
          productList: recommend
        })
        
      })
    }
    
    render() {
      const {  title, banners, productList} = this.state
      // 
      return (
        <div className='main'>
          <div>main</div>
          <MyComponent banners={banners} title={title}/>
          <MyComponent banners={banners} title={title}/>
          <MainProductList productList={productList} />
        </div>
      )
    }
  }

  export default Main
  ```
复制代码
###### 3.4. 如果没有传递的话,希望有默认值;

*
  1. 使用`defaultProps`就可以了
3.4. 子组件传递父组件
复制代码
###### 3.4.1. 某些情况,我们也需要子组件向父组件传递消息:

*
  1. 在Vue中是通过自定义事件来完成的;
*
  2. 在React中同样是`通过props传递消息`,只是`让父组件给子组件传递一个回调函数`,`在子组件中调用这个函数即可`;
复制代码
###### 3.4.2. 这里完成一个案例

*
  1. 将计数器案例进行拆解
*
  2. 将按钮封装到子组件中:CounterButton;
*
  3. CounterButton发生点击事件,将内容传递给父组件,修改counter的值;
复制代码
###### 3.4.3. 示例代码如下:

* 父组件:App.jsx

```js
  import React, { Component } from 'react'
  import AddCount from './AddCounter'
  import SubCount from './SubCounterr'

  export class App extends Component {
    constructor() {
      super()
      this.state = {
        counter: 0
      }
    }
    changeCounter (counter) {
      this.setState({
        counter: this.state.counter + counter
      })
    }
    render() {
      const { counter } = this.state
      return (
        <div>
          <h2>当前计数:{counter}</h2>
          <AddCount addClick={counter => this.changeCounter(counter) } />
          <SubCount subClick={counter => this.changeCounter(counter) } />
        </div>
      )
    }
  }

  export default App
```

* 子组件:AddCounter.jsx

```js
  import React, { Component } from 'react'

  export class AddCount extends Component {
    addCount(count) {
      // 拿到父组件传递过来的函数,把参数传递到函数里面,相当于把子组件的传递给父组件传参
      this.props.addClick(count)
    }
    render() {
      return (
        <div>
          <button onClick={e => this.addCount(1)}>+1</button>
          <button onClick={e => this.addCount(5)}>+5</button>
          <button onClick={e => this.addCount(10)}>+10</button>
        </div>
      )
    }
  }

  export default AddCount
```

* 子组件:SubCounter.j

```js
  import React, { Component } from 'react'

  export class subCount extends Component {
    subCount (count) {
      this.props.subClick(count)
    }
    render() {
      return (
        <div>
          <button onClick={e => this.subCount(-1)}>-1</button>
          <button onClick={e => this.subCount(-5)}>-5</button>
          <button onClick={e => this.subCount(-10)}>-10</button>
        </div>
      )
    }
  }

  export default subCount
```
3.5. 子组件传父组件案例练习
  • 1.如下图

    1. 父组件App代码:
    js 复制代码
      import React, { Component } from 'react'
      import TabControl from './TabControl'
    
      export class App extends Component {
        constructor() {
          super()
    
          this.state = {
            titles: ['流行', '新款', '热门'],
            tabIndex: 0
          }
        }
        tabClick (index) {
          this.setState({
            tabIndex: index
          })
        }
        render() {
          const { titles,tabIndex } = this.state
          return (
            <div className='app'>
              <TabControl titles={ titles } tabClick={i => this.tabClick(i)}/>
                <h2>{titles[tabIndex]}</h2>
            </div>
          )
        }
      }
    
      export default App
    ```·
    1. 子组件TabControl代码
    js 复制代码
      import React, { Component } from 'react'
      import './style.css'
    
    
      export class TabControl extends Component {
        constructor() {
          super()
          this.state = {
            currentIndex: 0
          }
        }
    
        changeTab (index) {
          this.props.tabClick(index)
          this.setState({
            currentIndex: index
          })
        } 
    
        render() {
          const { titles } =  this.props
          const { currentIndex } = this.state
          return (
            <div>
              <div className="tab-control">
                  {
                    titles.map((item, index) => {
                      return (
                      <div 
                        className={`item ${currentIndex === index ? 'active' : ''}`} 
                        key={index} 
                        onClick={(e) => this.changeTab(index)}>
                        <span className='text'>{item}</span>
                      </div>
                      )
                    })
                  }
              </div>
            </div>
          )
        }
      }
    
      export default TabControl