React 组件之间是怎么通信的呢?
前言
在学习React的过程中,需要用到很多组件之间传递数据的场景,根据需求复杂程度,我们有以下几种组件通信模式:
- 父组件向子组件通信
- 子组件向父组件通信
- 跨级组件之间通信
- 非嵌套组件间通信
根据组件关系的不同,我们用不同的处理方式解决,以下是具体处理方式。
父组件向子组件通信
这是最简单的通信模式,如图:

如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class List extends Component { render() { return ( <div> <h1>this is List</h1> <h2>The Index component data is: {this.props.data}</h2> </div> ); } }
class Index extends Component { render() { return ( <div> <h1>this is Index</h1> <List data={"Index Content"} /> </div> ); } }
|
我们只要将传的数据在父组件prop直接向下传递即可。
子组件向父组件通信
当子组件需要向父组件传递信息的时候,我们可以使用父组件props给子组件的回调函数,利用在函数中的参数传递信息,比如需要向父组件传递一个颜色值,改变父组件文字的颜色。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| class List extends Component { render() { return ( <div> <h1>this is List</h1> <button onClick={()=>this.props.changeColor('red')} >changeIndexColor</button> </div> ); } }
class Index extends Component { constructor(){ super(); this.state={ color:'green' } } changeColor(color){ this.setState({ color:color }) } render() { return ( <div> <h1 style={{color:this.state.color}} >this is Index</h1> <List changeColor={this.changeColor.bind(this)} /> </div> ); } }
|
以上两种组件通信方式较为简单,但是当组件之间关系比较复杂,这个时候数据传递起来遍不是那么得心应手,这个时候我们可以“浅试”一下两种方式,为什么说是“浅试”,因为官方并不推荐你这么做。
跨级组件之间的通信
概念
跨级组件通信,就是父组件与“子子”组件的通信,向更深层的子组件通信,如图:

对于这种组件间通信我们可以考虑一下两种方式:
- 父组件向子组件层层传递props
- 使用context对象
第一种方式中,如果父子组件层次比较浅可以考虑使用props传递,但是当层次非常深时,那么就显得很臃肿了。
contxet相当于一个全局的变量,是一个父组件给其子组件共享的大容器,里面可以放置要通信的各种变量与函数,注意,只能子组件访问父组件的context,这样,当嵌套再深,也不是什么问题,不需要层层传递,子组件直接从最顶层的父组件获取即可。
方法
使用context需要满足以下两个条件:
- 父组件需要申明自己支持context,定义一个childContextTypes表明context中属性的PropTypes,并且还需要提供一个函数getChildContext,用来返回相应的context对象
- 子组件需要定义一个contextTypes静态变量表示自己所需要父组件哪些context对象
看起来简单,但是操作起来却没那么方便,因为React并不想你使用过多的context,context本身就是一个非常危险的方式,就像所有人都建议你不要使用全局变量一样。
需求:我们假设做这样一个demo,子子组件获取到顶层父组件的state属性设置自己文字颜色,和获取父组件的函数,改变父组件的文字大小。
父子组件Index,需要满足要求:父组件需要申明自己支持context,定义一个childContextTypes表明context中属性的PropTypes,并且还需要提供一个函数getChildContext,用来返回相应的context对象,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| class Index extends Component { constructor() { super(); this.state = { listItemcolor: "blue", indexSizesize: "30px" }; } static childContextTypes = { listItemcolor: PropTypes.string, changeIndexSize: PropTypes.func };
getChildContext() { return { listItemcolor: this.state.listItemcolor, changeIndexSize: this.changeIndexSize.bind(this) }; }
changeIndexSize(size) { this.setState({ indexSizesize: size }); }
render() { const indexSize = {fontSize:this.state.indexSizesize} return ( <div> <h1 style={indexSize} > this is Index </h1> <List /> </div> ); } }
|
子组件List:
1 2 3 4 5 6 7 8 9 10
| class List extends Component { render() { return ( <div> <h1>this is List</h1> <ListItem /> </div> ); } }
|
子子组件ListItem,满足要求:子组件需要定义一个contextTypes静态变量表示自己所需要父组件哪些context对象,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| class ListItem extends Component { static contextTypes = { listItemcolor: PropTypes.string, changeIndexSize: PropTypes.func };
render() { const style = { color: this.context.listItemcolor }; const changeIndexSize = size => { return () => { this.context.changeIndexSize(size); }; }; console.log(changeIndexSize); return ( <div> <h2 style={style}>this is listItem</h2> <button onClick={changeIndexSize("60px")}>change Index's Size</button> </div> ); } }
|
总结
context增强了组件之间的耦合性,并且context就像全局变量一样,可以随意被修改,即子组件都能修改context的内容,导致程序出现难查的BUG,因此非常不建议大量使用。但是contrxt的在React具有很重要的地位,因为基本上所有的第三方状态管理库(Redux,Flux)都充分利用了context的机制,使得我们再大项目中完美的管理好复杂的状态,我们只要用好这些管理工具就好了。
非嵌套组件之间通信
概念
非嵌套组件,就是关系性很弱甚至没有包含关系的组件,就像如下图:

按照我们之前用过的三种方式,我们可以采用以下两种:
- 利用二者的父组件的context对象进行通信
- 利用自定义事件通信
我们权衡第一种方式,第一它采用组件的共同父级来进行中转,会增加子组件与父组件之间的耦合度,当两者共同的父组件出现修改时,指不定就会影响到context的传递。第二,如果两组件之间的层次非常非常深,找到两者的共同组件是一件不容易的事,emmmm,不是不可,只是不推荐这么做。
因此,我们采用一种全新的方式:自定义事件
方法
先下载自定义事件需要的包(create-react-app不会自带):
1
| npm install events --save
|
在更目录下新建一个events.js文件,引入events包,并且向外提供事件对象,供组件之间通信时使用:
1 2 3
| import { EventEmitter } from 'events'; export default new EventEmitter();
|
共同父组件Index:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import React, { Component } from "react"; import ReactDOM from "react-dom"; import List from "./List"; import Footer from "./Footer";
class Index extends Component { render() { return ( <div> <List /> <Footer /> </div> ); } }
ReactDOM.render(<Index />, document.getElementById("root"));
|
List组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import React, { Component } from "react"; import emitter from "./events";
export default class List extends Component { constructor(props) { super(props); this.state = { color: "green" }; }
componentDidMount() { this.eventEmitter = emitter.addListener("changeColor", color => { this.setState({ color }); }); }
componentWillUnmount() { emitter.removeListener(this.eventEmitter); } render() { const style = { color: this.state.color }; return ( <div> <h1 style={style}>This is List</h1> </div> ); } }
|
Footer组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import React, { Component } from "react"; import FooterBox from "./FooterBox";
export default class Footer extends Component { render() { return ( <div> <h1>This is Footer</h1> <FooterBox /> </div> ); } }
|
FooterBox组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import React, { Component } from "react"; import emitter from "./events";
export default class FooterBox extends Component { render() { const changeColor = color => { return () => { emitter.emit("changeColor", color); }; }; return ( <div> <h1>This is FooterBox</h1> <button onClick={changeColor("Blue")}> FooterBox change List's textColor </button> </div> ); } }
|
效果图:

总结
了解发布/订阅模式的同学便很容易理解,通过向事件对象上添加监听器出发事件来实现组件间的通信,这种方式对比于context来说更加简单粗暴,但是滥用自定义events会积攒较多的“全局事件”,可能会导致不可预料的BUG发生。
最后总结
以上四种组件通信方式涵盖了我们大部分开发场景,第一二种是我们常用到的,官方文档也推荐我们这么用,但是第三四种是在比较复杂的开发场景下使用的,官方并不推崇使用第三四种方式实现,因为当业务复杂性越来越高时,会产生极大的耦合度,影响后期的更新维护,这个时候我们便需要引入状态管理工具,Flux、Redux、Mobx都是比较好的选择,关于状态管理工具后续也会有博文讲解。
nice to meet you~