土豆博客

React之组件通讯的几种方式

# 一、概述

学过Vue的同学应该知道,在Vue中有很多中通讯方案,那么在React使用过程中,当然也会不可避免的使用到组件通讯,常见的几种情况如下:

1.父组件到子组件

2.子组件到父组件

3.跨级组件

4.非嵌套组件

下面依次讲解上面几种常用的通讯方式

# 二、父组件到子组件

通常父组件使用props向子组件传递,即向子组件上添加各种属性,然后子组件通过this.props.xxx的方式获取传递的数据,也可以使用ES6的解构赋值获取

//Parent.jsx
import React, { Component } from "react";
import Child from "./Child";

export default class Parent extends Component {
  render() {
    return (
      <div>
        <p>我是父组件</p>
        <Child name="xtd" age={23} />
      </div>
    );
  }
}

//Child.jsx
import React, { Component } from "react";

export default class Child extends Component {
  render() {
    const { name, age } = this.props;
    return (
      <div>
        <p>
          子组件:父亲传递过来的数据:{name}——{age}
        </p>
      </div>
    );
  }
}
# 三、子组件到父组件

利用回调函数:父组件将一个函数作为 props 传递给子组件,子组件调用该回调函数,便可以向父组件通信。

//Parent.jsx
import React, { Component } from "react";
import Child from "./Child";

export default class Parent extends Component {
  getChild(msg) {
    console.log("我是父组件,我接受到的子组件的数据为:" + msg);
  }
  render() {
    return (
      <div>
        <p>我是父组件</p>
        <Child getMsg={this.getChild.bind(this)} />
      </div>
    );
  }
}
//Child.jsx
import React, { Component } from "react";

export default class Child extends Component {
  constructor(props) {
    super(props);
    this.state = {
      msg: "子组件上的数据"
    };
  }
  clickHandle() {
    this.props.getMsg(this.state.msg);
  }
  render() {
    return (
      <div>
        <button onClick={this.clickHandle.bind(this)}>传递给父亲</button>
      </div>
    );
  }
}

利用Refs:Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。你可以使用 React.createRef() 创建Refs,同时给子组件调用处设置相应的属性,然后就直接通过this.xxx调用即可

//Parent.jsx
import React, { Component } from "react";
import Child from "./Child";

export default class Parent extends Component {
  constructor(props) {
    super(props);
    this.child = React.createRef();
  }
  getChild() {
    console.log(this.child.current);
  }
  render() {
    return (
      <div>
        <p>我是父组件</p>
        <button onClick={this.getChild.bind(this)}>获取子组件</button>
        <Child ref={this.child} />
      </div>
    );
  }
}
//Child.jsx
import React, { Component } from "react";

export default class Child extends Component {
  constructor(props) {
    super(props);
    this.state = {
      msg: "子组件上的数据"
    };
  }
  render() {
    return (
      <div>
        <p>我是子组件</p>
      </div>
    );
  }
}

# 四、跨级组件

所谓跨级组件通信,就是父组件向子组件的子组件通信,向更深层的子组件通信,如下图:

跨级组件通信可以采用下面两种方式:

  • 中间组件层层传递 props
  • 使用 Context

对于第一种方式,如果父组件结构较深,那么中间的每一层组件都要去传递 props,增加了复杂度,并且这些 props 并不是这些中间组件自己所需要的。不过这种方式也是可行的,当组件层次在三层以内可以考虑采用这种方式,当组件嵌套过深时,这种方案就过于臃肿,如下图:

使用 Context 是另一种可行的方式,Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。

你可以把它当于一个全局变量,是一个大容器,有点类似于Vue中的EventBus,我们把要通信的内容放在这个容器中,故这个容器被称作为Provider(提供者),而使用这个容器中的数据的被称为Consumer(消费者)

/* utils/context.js */

import React from "react";
//调用react的createContext()方法, 产生生产者和消费者组件.
let { Consumer, Provider } = React.createContext();
export { Consumer, Provider };

在父(祖先)级组件中, 把要传递东西的后代组件用Provider包起来, 要传递的东西放进value里面,而后代组件中的组件放在Consumer里面, 内部是一个函数, 这个函数接受一个对象作为参数, 参数是Provider里面提供的值

//Parent.jsx 父组件
import React, { Component } from "react";
import Child from "./Child";
import { Provider } from "../utils/contenxt";

export default class Parent extends Component {
  render() {
    return (
      // 使用value属性设置要传递的数据
      <Provider value={{ name: "xtd", age: 23 }}>
        <p>我是父组件</p>
        <Child />
      </Provider>
    );
  }
}

//Child.jsx 子组件
import React, { Component } from "react";
import Sun from "./Sun";
export default class Child extends Component {
  render() {
    return (
      <div>
        <p>我是子组件</p>
        <Sun />
      </div>
    );
  }
}

//Sun.jsx 孙子组件
import React, { Component } from "react";
import { Consumer } from "../utils/contenxt";
export default class Sun extends Component {
  render() {
    return (
      <Consumer>
        {(value) => (
          <p>
            {value.name}-{value.age}
          </p>
        )}
      </Consumer>
    );
  }
}
# 五、非嵌套组件

非嵌套组件,就是没有任何包含关系的组件,包括兄弟组件以及不在同一个父级中的非兄弟组件。例如下面这种情况,此时孙子A需要与孙子B进行通信

在这里我们采用自定义事件的方式来实现非嵌套组件间的通信,我们需要使用一个events包:

yarn add  events 
//或者
npm i events --save

utils目录下新建一个 events.js,引入 events 包,并向外提供一个事件对象,供通信时使用:

import { EventEmitter } from "events";
export default new EventEmitter();
//SunB.jsx
import React, { Component } from "react";
import emitter from "../utils/events";
export default class SunB extends Component {
  //组件装载完成以后声明一个自定义事件
  componentDidMount() {
    this.eventEmitter = emitter.addListener("getMsg", (msg) => {
      console.log(msg);
    });
  }
  // 组件销毁前移除事件监听
  componentWillUnmount() {
    emitter.removeListener(this.eventEmitter);
  }
  render() {
    return (
      <div>
        <p>我是孙子B</p>
      </div>
    );
  }
}


//SunA.jsx
import React, { Component } from "react";
import emitter from "../utils/events";
export default class Sun extends Component {
  constructor(props) {
    super(props);
    this.state = {
      msg: "我是孙子A中的消息"
    };
  }
  clickHandle = () => {
    emitter.emit("getMsg", this.state.msg);
  };
  render() {
    return (
      <div>
        <p>我是孙子A</p>
        <button onClick={this.clickHandle}>传递给孙子B</button>
      </div>
    );
  }
}

自定义事件是典型的发布/订阅模式,通过向事件对象上添加监听器和触发事件来实现组件间通信。

当然,在面临大型项目的时候,如果需要传递和共享的数据很多,用户的使用方式复杂,View要从多个来源获取数据,上述的这些组件间的通信还是太难以管理了,因此出现了很多状态管理工具,如 fluxredux 等,使用这些工具使得组件间的通信更容易追踪和管理。

code
top

扫码添加,一起进步

wechat-code

为了保障最佳预览体验,博客已不支持IE浏览器的访问,邀请您使用以下现代高级浏览器。

谷歌浏览器(推荐) 火狐浏览器

注:如果你使用的是360,QQ等双核浏览器,请开启极速模式