# 一、概述
学过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要从多个来源获取数据,上述的这些组件间的通信还是太难以管理了,因此出现了很多状态管理工具,如 flux
、redux
等,使用这些工具使得组件间的通信更容易追踪和管理。