React 组件之间到底是如何进行通讯的?

111 阅读5分钟

本文已参与「新人创作礼」活动, 一起开启掘金创作之路。

React 组件进阶——组件通讯

目录

组件通讯介绍

组件是独立且封闭的单元,默认情况下,只能使用组件自己的数据。在组件化过程中,我们将一个完整的功能拆分成多个组件,以更好地完成整个应用的功能。而在这个过程中,多个组件之间不可避免的要共享某些数据。为了实现这些功能,就需要打破组件的独立封闭性,让其与外界沟通。这个过程就是组件通讯

组件的 prop

  1. 组件是封闭的,要接收外部数据应该通过 props 来实现。

  2. props 的作用:接收传递给组件的数据。

  3. 传递数据:给组件标签添加属性

    <hello name="jack" age={19} />
    
  4. 接收数据:函数组件通过参数 props 接收数据,类组件通过 this.props 接收数据。

    // 函数组件接收数据
    
    function Hello(props) {
      console.log(props)
      return (
      	<div>接收到数据:{props.name}</div>
      )
    }
    
    // 类组件接收数据
    
    class Hello extends React.Component {
      render() {
        return (
        	<div>接收到的数据:{this.props.age}</div>
        )
      }
    }
    <hello name="jack" age={19} />
    
  5. 特点

    • 可以给组件传递任意类型的数据。
    • props只读的对象,只能读取属性的值,无法修改对象。
    • 注意:使用类组件时,如果写了构造函数,**应该将 props 传递给 super() **,否则,无法在构造函数中获取到 props!
    class Hello extends React.Component {
      constructor(props){
        // 推荐将 props 传递给父类构造函数
        super(props)
      }
      render() {
        return (
        	<div>接收到的数据:{this.props.age}</div>
        )
      }
    }
    <hello name="jack" age={19} />
    

组件通讯的三种方式

父组件传递数据给子组件

  1. 父组件提供要传递的 state 数据。

  2. 给子组件标签添加属性,值为 state 中的数据。

  3. 子组件中通过 props 接收父组件中传递的数据。

    class Hello extends React.Component {
      state = {
        lastName: '王'
      }
    	render() {
        return (
          <div>传递给子组件:<Child name={this.state.lastName}></Child></div>
        )
      }
    }
    
    // 子组件
    function Child(props) {
      return <div>子组件接收到的数据:{props.name}</div>
    }
    

子组件传递数据给父组件

  1. 思路:利用回调函数,父组件提供回调,子组件调用,将要传递的数据作为回调函数的参数。

    • 父组件提供一个回调函数(用于接收数据)。
    • 将该函数作为属性的值,传递给子组件。
    • 子组件通过 props 调用回调函数。
    • 将子组件的数据作为参数传递给回调函数。
    class Parent extends React.Components {
      // 获取子组件的函数
      getChildMsg = (msg) => {
        console.log('接收到子组件的数据',msg)
      }
      
      render() {
        return (
        	<div>
          	子组件:
            <Child getMsg={this.getChildMsg} />
          </div>
        )
      }
    }
    
  2. 注意:回调函数中的 this 指向问题!!!

  3. 小案例介绍子组件向父组件传递数据:

    class Parent extends React.Component {
      state = {
        parentMsg: ''
      }
      // 获取子组件的函数
      getChildMsg = data => {
        console.log('接收到子组件的数据',data)
        this.setState({
          parentMsg: data
        })
      }
    
      render() {
        return (
          <div style={{backgroundColor: 'red'}}>
            父组件:{this.state.parentMsg}
            <div style={{backgroundColor: 'blue', color: '#fff'}}>
              子组件:
              <Child getMsg={this.getChildMsg} />
            </div>
          </div>
        )
      }
    }
    
    class Child extends React.Component {
      state = {
        childMsg: '刷抖音'
      }
      handleClick = () => {
        this.props.getMsg(this.state.childMsg)
      }
      render() {
        return (
          <div>
            <button onClick={this.handleClick}>点我向父组件传递数据</button>
          </div>
        )
      }
    }
    

兄弟组件

  1. 没直接嵌套关系的两个组件是兄弟组件。

  2. 共享状态提升到最近的公共父组件中,由公共父组件管理这个状态。

  3. 思想:状态提升。

  4. 公共父组件职责:

    • 提供共享状态。
    • 提供操作共享状态的方法。
  5. 要通讯的子组件只需通过 props 接收状态或操作状态的方法。

    class Counter extends React.Component {
      // 提供共享状态
      state = {
        count: 0
      }
      // 提供修改状态的方法
      onIncrement = () => {
        this.setState({
          count: this.state.count + 1
        })
      }
      render() {
        return (
          <div>
            <Child1 count={this.state.count} />
            <Child2 onIncrement={this.onIncrement} />
          </div>
        )
      }
    }
    const Child1 = props => {
      return <h1>计数器:{props.count}</h1>
    }
    const Child2 = props => {
      return <button onClick={() => props.onIncrement()}>+1</button>
    }
    

Context


  1. 思考:App 组件要传递数据给 Child 组件,该如何处理?

    • 处理方式:使用 props 一层层组件往下传递(繁琐)。

    • 更好的方式:使用 Context 。

      • 作用:跨组件传递数据(比如:主题、语言等)。
  2. 使用步骤:

    • 调用 React.createContext() 创建 Provider(提供数据)和 Consumer(消费数据)两个组件。

      const { Provider, Consumer } = React.createContext()
      
    • 使用 Provider 组件作为父节点。

      <provider>
      	<div>
        	<Child1 />
        </div>
      </provider>
      
    • 设置 value 属性,表示要传递的数据。

      <Provider value="pink" ></Provider>
      
    • 调用 Consumer 组件接收数据。

      <Consumer>
      	{data => <span>data 参数表示接收到的数据 -- {data}</span>}
      </Consumer>
      
  3. 案例讲解:

    const {Provider, Consumer} = React.createContext()
    class App extends React.Component {
      render() {
        return (
          <Provider value="pink">
            <div className="app">
              <Node />
            </div>
          </Provider>
        )
      }
    }
    const Node = props => {
      return (
        <div className="node">
          <SubNode />
        </div>
      )
    }
    const SubNode = props => {
      return (
        <div className="subNode">
          <Child />
        </div>
      )
    }
    const Child = () => {
      return (
        <Consumer>
          {
            data => <div className="child">我是子节点--{data}</div>
          }
        </Consumer>
      )
    }
    
  4. 总结:

    • 如果两个组件是远房亲戚(比如,嵌套多层)可以使用 Context 实现组件通讯。

    • Context 提供了两个组件:Provider 和 Consumer 。

      • Provider 组件:用来提供数据。
      • Consumer 组件:用来消费数据。

props 深入

children 属性

  1. children 属性:表示组件标签的子节点。当组件标签有子节点时,props 就会有该属性。

  2. children 属性与普通的 props 一样,值可以是任意值(文本、React 元素、组件,甚至是函数)。

    const App = props => {
      console.log(props)	// 输出的是:我是子节点。
      return (
      	<div>
        	<h1>组件标签的子节点:</h1>
          {props.children}
        </div>
      )
    }
    
    ReactDOM.render(<Hello>我是子节点</Hello>, document.querySelector('#root')
    

props 校验

  1. 对于组件来说,props 是外来的,无法保证组件使用者传入什么格式的数据。

  2. 如果传入的数据格式不对,可能会导致组件内部报错。

  3. 关键问题:组件的使用者不知道明确的错误原因。

  4. props 校验:允许在创建组件的时候,就指定 props 的类型、格式等。

    • 作用:捕获使用组件时因为 props 导致的错误,给出明确的错误提示,增加组件的健壮性。
    App.propTypes = {
      colors: PropTypes.array
    }
    
  5. 使用步骤:

    • 安装包 prop-types(yarn add prop-types/npm i props-types)。
    • 导入 prop-types 包。
    • 使用组件名.propTypes = {}来给组件的 props 添加校验规则。
    • 校验规则通过 PropTypes 对象来指定。
    import PropTypes from 'prop-types'
    
    function App(props) {
      return (
      	<h1>Hi,{props.colors}</h1>
      )
    }
    
    App.propTypes = {
      // 约定 colors 属性为 array 类型。
      // 如果类型不对,则会报出明确错误,便于分析错误原因。
      colors: PropTypes.array
    }
    
  6. 约束规则:

    • 常见类型:array、bool、func、number、object、string。

    • React 元素类型:element。

    • 必填项:isRequired。

    • 特定结构的对象:shape({ })。

      // 常见类型
      optionalFunc: PropTypes.func,
      // 必选
      requiredFunc: PropTypes.func.isRequired,
      // 特定结构的对象
      optionalObjectWithShape: Proptypes.shape({
        color: PropTypes.string,
        fontSize: PropTypes.number
      })
      

props 的默认值

  • 场景:分页组件 → 每页显示条数

  • 作用:给 prpps 设置默认值,在为传入 props 时生效。

    function App(props) {
      return (
      	<div>此处展示 props 的默认值:{props.pageSize}</div>
      )
    }
    // 设置默认值
    App.defaultProps = {
      pageSize: 10
    }
    // 不传入 pageSize 属性
    <App />