知识总结 React【组件通讯】

515 阅读6分钟

组件通讯

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

props

  • props的作用:接收传递给组件的数据
  • props的值:是标签属性组成对象。
  • props名字:属性-property单词的缩写prop , + s

使用步骤:

  • 传递数据:给组件标签添加属性。 -- 与vue类似
  • 接收数据:函数组件通过参数props接收数据,类组件通过this.props接收数据

父组件:

export default class App extends Component {
  render() {
    return <div>
      <Tie name={'daodao'} age={21}></Tie>
      <Chao  name={'chaochao'} age={21}></Chao>
    </div>
  }
}

函数组件通讯

通过参数props接收数据,由于是参数,因此可以修改名字

function Tie({ name, age }) {
  // console.log(props);
  return <div>{name},{age}</div>
}

类组件通讯

通过this.props接收数据,由于是对象属性,因此不能修改名字

class Chao extends React.Component {
  render() {
    console.log(this);
    const { name,age } = this.props
    return <div>{name},{age}</div>
  }
}

注意:

  1. 推荐解构props
  2. props是对象,标签属性组成的对象。
  3. React中props绕不过引用地址。

props的特点

  • 可以给组件传递任意类型的数据,包括虚拟 DOM 结构

    • string、number、null、undefined、boolean

      null、undefined、boolean传是可以传,控制台打印出来也能看到,但是直接使用的话不会显示,想要显示要用 String() 转为字符串。

    • 数组、对象

      对象不能直接放进插值里,会报错,要通过 JSON.stringify() 转为字符串;数组可以直接使用,会把每一项转为单独的 DOM 结构。

    • 函数、JSX

      相当于父组件传递一个回调函数,子组件形参接收回调函数并使用。

    export default class App extends Component {
      render() {
        return (
          <div>
            {/* null undefined boolean  */}
            <Child name={null} age={undefined} gender={true}></Child>
            {/* object  */}
            <Child person={{ zs: 'zs' }}></Child>
            {/* 数组 */}
            <Child list={[1, 2, 3, 4]}></Child>
            {/* function */}
            <Child fn={() => alert(123)}></Child>
            {/* JSX  */}
            <Child msg={<i>我是props传来的JSX</i>}></Child>
          </div>
        );
      }
    }
    ​
    function Child({ msg, name, age, gender, person, list, fn }) {
      console.log('fn  ----->  ', fn);
      return (
        <div>
          {/* number string  */}
          我是子组件 - {String(name)} - {age}
          <h1>{gender}</h1>
          {JSON.stringify(person)}
          <h2>{list}</h2>
          <button onClick={fn}>点我</button>
          {msg}
        </div>
      );
    }
    
  • props是只读的,不允许修改props的数据 - 单向数据流

    export default class App extends Component {
      render() {
        return (
          <div>
            {/* JSX  */}
            <Child msg={<i>我是props传来的JSX</i>}></Child>
          </div>
        );
      }
    }
    ​
    function Child() {
      return (
        <div>
          {msg}
          <button
            onClick={() => {
              msg = '123';
            }}
          >
            点我修改props
          </button>
        </div>
      );
    }
    
  • 不必先定义后使用,可以只传不接收。 (与Vue不同)

    export default class App extends Component {
      render() {
        return (
          <div>
            {/* 1. props不必先定义后使用 */}
            <Child name="zs123" age={18123} gender={'xxxx'}></Child>
          </div>
        );
      }
    }
    ​
    function Child() {
      return (
        <div>我是子组件</div>
      );
    }
    

总结:

  1. props是对象
  2. props可以是任意值,注意,function是对象,不能作为标签的内容

组件通讯三种方式

  • 父传子
  • 子传父
  • 兄弟组件

父传子

  1. 父组件提供要传递的state数据
  2. 给子组件标签添加属性,值为 state 中的数据
  3. 子组件中通过 props 接收父组件中传递的数据
import React, {Component} from 'react';
​
export default class Parent extends Component {
  state = {
    money: 1000,
  };
​
  handleMakeMoney = () => {
    this.setState({
      money: 1000 + this.state.money,
    });
  };
​
  render() {
    return (
      <div>
        <button onClick={this.handleMakeMoney}>爸爸开始赚钱了</button>        
        <Child money={this.state.money}></Child>
      </div>
    );
  }
}
​
class Child extends React.Component {
  render() { 
    const { money } = this.props
    return (
      <div>
        <h1>爸爸给我钱了: {money}</h1>        
      </div>
    );
  }
}

子传父

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

  1. 父组件提供一个回调函数(用于接收数据)
  2. 将该函数作为标签属性的值,传递给子组件
  3. 子组件通过 props.语法找到函数,再 调用回调函数
  4. 将子组件的数据作为参数传递给回调函数
export default class Parent extends Component {
  state = {
    money: 1000,
  };
​
  // 1. 定义一个函数
  handleCostMoney = (num) => {
    this.setState({
      money: this.state.money - num,
    });
  };
​
  render() {
    return (
      <div>
        // 2. 为子组件传递该回调函数
        <Child handleCostMoney={this.handleCostMoney}></Child>
      </div>
    );
  }
}
​
class Child extends React.Component {
  render() { 
    const { handleCostMoney } = this.props
    return (
      <div>
        // 3. 子组件在点击事件中调用
        <button onClick={() => handleCostMoney(100)}>点我花钱</button>    
      </div>
    );
  }
}

兄弟组件

状态提升,把state内的数据从组件中提取到两个兄弟组件的父组件上,通过子传父和父传子来共同操作父组件的数据。

拓展:快速重构小技巧

提取父组件中的某个部分封装为子组件,可以把那部分代码选中,点击右键,选择重构,选择 “提取到 class Appmethod 中 ” 即可。

重构

组件通讯

实现步骤

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

    const { Provider, Consumer } = React.createContext()
    
  • 使用 Provider 组件作为父节点。设置 value 属性,表示要传递的数据。

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

    <Consumer>
      {(data) => {
        return <span>{data}</span>
      }}
    </Consumer>
    

总结:

  1. 如果两个组件是远方亲戚(比如,嵌套多层)可以使用Context 实现组件通讯
  2. Context 提供了两个组件:ProviderConsumer
  3. createContext() 可以调用多次,多次解构会重名,需要重命名。每次解构出来的ProviderConsumer 要成对使用。
  4. Provider 组件:用来存入数据
  5. Consumer 组件:用来取出数据

优缺点

优点:React 自带,不需要借助其它包,即可跨组件通信

缺点:ProviderConsumer 增加嵌套结构,代码理解成本加大。

props深入

children属性

children属性:表示该组件的子节点,只要组件有子节点,props就有该属性

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

function Hello(props) {
  return (
    <div>
      该组件的子节点:{props.children}
    </div>
  )
}
​
<Hello>我是子节点</Hello>
​

模拟插槽

  • 具名插槽

    使用父组件传过来的对象或数组。

  • 作用域插槽

    children是一个函数,可以主动调用传参,传递什么参数父组件就能用什么参数。

    export default class App extends Component {
      render() {
        return <div>
          <Child>{(data) => {
            console.log(data);
          }}</Child>
        </div>
      }
    }
    ​
    class Child extends Component {
      state = {
        list: ['chao', 'yu', 'ze']
      }
      render() {
        return <h1>{this.props.children(this.state.list)}</h1>
      }
    }
    

复习

props校验

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

如果传入的数据格式不对,可能会导致组件内部报错。组件的使用者不能很明确的知道错误的原因。 props 校验允许在创建组件的时候,就约定props的格式、类型等。

作用:规定接收的props的类型,如果就会报错,增加组件的健壮性。

使用步骤

  1. 导入 prop-types 包 (不用下载,脚手架自带)

    import PropTypes from "prop-types";
    
  2. 使用:组件名.propTypes = {} 来给组件的props添加校验规则对象

    子组件名.propTypes = {
      数据变量名: PropTypes.类型
    }
    
  3. 规则的数据类型,通过 PropTypes 对象来指定

import PropTypes from "prop-types";
​
export default class App extends Component {
  render() {
    return <div>
      <Child msg={[1,2,3]}></Child>
    </div>
  }
}
​
class Child extends Component {
  render() {
    return <div></div>
  }
}
​
Child.propTypes = {
  msg: PropTypes.string
}

约束规则

  1. 常见类型:array、bool、func、number、object、string
  2. React元素类型:element
  3. 必填项:isRequired
  4. 特定结构的对象:shape
// 常见类型
optionalFunc: PropTypes.func,
// 必选
requiredFunc: PropTypes.func.isRequired,
// 特定结构的对象
optionalObjectWithShape: PropTypes.shape({
    color: PropTypes.string,
    fontSize: PropTypes.number
})

注意:函数式组件的校验,语法上没有什么不同。

props默认值

通过 defaultProps 可以给组件的props设置默认值,在未传入props的时候生效。

  • 函数组件

    function App({pageSize: 10}) {
      return (
        <div>
          此处展示props的默认值:{pageSize}
        </div>
      )
    }
    
  • 类组件

    class App extends Component {
      render() {
        return (
          <div>
            此处展示props的默认值:{this.props.pageSize}
          </div>
        )
      }
    }
    // 设置默认值
    App.defaultProps = {
        pageSize: 10
    }
    

类的静态属性

  • 实例成员:通过实例对象调用的属性或者方法,叫做实例成员(属性或者方法)
  • 静态成员:通过类或者构造函数本身才能访问的属性或者方法
class Person {
  // 实例属性
  name = 'zs'
    // 实例方法
  sayHi() {
    console.log('哈哈')
  }

  // 静态属性
  static age = 18
    // 静态方法
  static goodBye() {
    console.log('byebye')
  }
}
const p = new Person()

console.log(p.name) // 访问实例属性
p.sayHi() // 调用实例方法

console.log(Person.age) // 访问静态属性
Person.goodBye() // 调用静态方法