React组件间的通讯

组件化开发应该是React核心功能之一,组件之间的通讯也是我们做React开发必要掌握的技能。接下来我们将从组件之间的关系来分解组件间如何传递数据。
1.父组件向子组件传递数据
通讯是单向的,数据必须是由一方传到另一方。在 React 中,父组件可以向子组件通过传 props 的方式,向子组件进行通讯。
// 父组件 Parent
class Parent extends Component{
state = {
msg: 'start'
};
componentDidMount() {
setTimeout(() => {
this.setState({
msg: 'end'
});
}, 3000);
}
render() {
return (<Child1 msg={this.state.msg}/>);
}
}
// 子组件 Child1
class Child1 extends Component{
render() {
return (<p>{this.props.msg}</p>);
}
}
如果父组件与子组件之间不止一层嵌套,如 "Parent>Child1>Child1_1" 这样的关系,可通过 ... 运算符(Object 剩余和展开属性),将父组件的信息,以更简洁的方式传递给更深层级的子组件。通过这种方式,不用考虑性能的问题,通过 babel 转义后的 ... 运算符 性能和原生的一致,且上级组件 props 与 state 的改变,会导致组件本身及其子组件的生命周期改变。
// 向 Child1_1 传递 Parent 组件的信息
// Parent 的子组件 Child1
class Child1 extends Component{
render() {
return (
<div>
<p>{this.props.msg}</p>
<Child1_1 {...this.props}/>
</div>
);
}
}
// Child1 的子组件 Child1_1
class Child1_1 extends Component{
render() {
return (<p>{this.props.msg}</p>);
}
}
2.子组件向父组件传递数据
在上一个例子中,父组件可以通过传递 props 的方式,自顶而下向子组件进行通讯。而子组件向父组件传递数据,同样也需要父组件向子组件传递 props 进行通讯,只是父组件传递的是作用域为父组件自身的函数,子组件调用该函数,将子组件想要传递的信息,作为参数,传递到父组件的作用域中。
// 父组件 Parent
class Parent extends Component{
state = {
msg: 'start'
};
transferMsg(msg) {
this.setState({
msg
});
}
render() {
return (
<div>
<p>child msg: {this.state.msg}</p>
<Child1 transferMsg = {msg => this.transferMsg(msg)}/>
</div>
);
}
}
// 子组件 Child1
class Child1 extends Component{
componentDidMount() {
setTimeout(() => {
this.props.transferMsg('end');
}, 3000);
}
render() {
return (<p>child1 component</p>);
}
}
在上面的例子中,我们使用了 箭头函数,将父组件的 transferMsg 函数通过 props 传递给子组件,得益于箭头函数,保证子组件在调用 transferMsg 函数时,其内部 this 仍指向父组件。
当然,对于嵌套比较深的子组件与父组件之间的数据传递,仍可使用 ... 运算符,将父组件的调用函数传递给子组件,具体方法和上面的例子类似。
3.兄弟组件间传递数据
对于没有直接关联关系的两个节点,就如 Child1 与 Child2 之间的关系,他们唯一的关联点,就是拥有相同的父组件(即 Parent > Chid1, Parent > Child2, Child1 向 Child2 传递数据)。参考之前介绍的两种关系的通讯方式,如果我们向由 Child1 向 Child2 传递数据,我们可以先通过 Child1 向 Parent 组件进行通讯,再由 Parent 向 Child2 组件进行通讯,所以有以下代码:
(1)父组件Parent
import React, { Component } from 'react';
import Child1 from './components/Child1';
import Child2 from './components/Child2';
// 父组件 Parent
class Parent extends Component {
state = {
msg: 'start'
}
componentDidUpdate() {
console.log('Parent Update');
}
transferMsg(msg) {
this.setState({
msg
});
}
render() {
return (
<div style={{textAlign:'center'}}>
<Child1 transferMsg={msg=>this.transferMsg(msg)}/>
<hr/>
<Child2 msg={this.state.msg}/>
</div>
);
}
}
export default Parent;
(2)子组件Child1
import React,{Component} from 'react';
// 子组件 Child1
class Child1 extends Component{
state={
child1_data:'end'
}
componentDidMount() {
setTimeout(() => {
this.props.transferMsg(this.state.child1_data);
}, 3000);
}
componentDidUpdate(){
console.log('Child1 Update');
}
render() {
return (
<div>
<h3>Child1 component</h3>
<h4>Child1组件数据:{this.state.child1_data}</h4>
</div>
);
}
}
export default Child1;
(3)子组件Child2
import React,{Component} from 'react';
// 子组件 Child2
class Child2 extends Component{
componentDidUpdate() {
console.log('Child2 Update');
}
render(){
return (
<div>
<h3>Child2 component</h3>
<h4 style={{color:'red'}}>Child2组件数据:{this.props.msg}</h4>
</div>
);
}
}
export default Child2;
(4)运行结果

从运行结果来看是达到了我们想要的目的。然而这个方法有至少两个问题:
第一,一旦我们嵌套很多层的组件,难道我们真的要一层一层的通过this.props来完成数据传递吗?;
第二,由于 Parent 的 state 发生变化,会触发 Parent 及从属于 Parent 的子组件的生命周期,所以我们在控制台中可以看到,在各个组件中的 componentDidUpdate 方法均被触发(有没有注意代码中的console.log呢?):

有没有更好的解决方式来进行兄弟组件间的通讯,甚至是父子组件层级较深的通讯的呢?
4.观察者模式
观察者模式也叫 发布者-订阅者模式,发布者发布事件,订阅者监听事件并做出反应,对于上面的代码,我们引入一个小模块,使用观察者模式进行改造。在传统的前端解耦方面,观察者模式作为比较常见一种设计模式,大量使用在各种框架类库的设计当中。
我们假定,存在一个"信号中心" (SignalCenter),某个任务执行完成,就向信号中心"发布"(publish)一个信号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执行。
(1)兄弟组件之间的通讯
( Parent > Chid1, Parent > Child2, Child1 向 Child2 传递数据。)
(a)父组件Parent
import React, { Component } from 'react';
import Child1 from './components/Child1';
import Child2 from './components/Child2';
// 父组件 Parent
class Parent extends Component {
componentDidUpdate(){
console.log('Parent Update');
}
render() {
return (
<div style={{textAlign:'center'}}>
<Child1 />
<hr/>
<Child2 />
</div>
);
}
}
export default Parent;
(b)子组件Child1
import React,{Component} from 'react';
// SignalCenter 类似于信号中心
import SignalCenter from '../SignalCenter';
// 子组件 Child1
class Child1 extends Component{
state={
child1_data:'哎哟,不错哦!'
}
componentDidMount() {
setTimeout(() => {
// 发布msg事件
SignalCenter.publish('msg',this.state.child1_data);
}, 3000);
}
componentDidUpdate(){
console.log('Child1 Update');
}
render() {
return (
<div>
<h3>Child1 component</h3>
<h4>Child1组件数据:{this.state.child1_data}</h4>
</div>
);
}
}
export default Child1;
(c)子组件Child2
import React,{Component} from 'react';
// SignalCenter 类似于信号中心
import SignalCenter from '../SignalCenter';
// 子组件 Child2
class Child2 extends Component{
state={
msg:'start'
}
componentDidMount(){
// 监听(订阅)msg事件
SignalCenter.subscribe('msg',(msg)=>{
this.setState({
msg
})
});
}
componentDidUpdate() {
console.log('Child2 Update');
}
render(){
return (
<div>
<h3>Child2 component</h3>
<h4 style={{color:'red'}}>Child2组件数据:{this.state.msg}</h4>
</div>
);
}
}
export default Child2;
(d)运行结果

我们在 Child2 组件的 componentDidMount 中订阅了 msg 事件,并在 Child1 componentDidMount 中,在 3s 后发布了 msg 事件,Child2 组件对 msg 事件做出相应,更新了自身的 state ,我们可以看到,由于在整个通讯过程中,只改变了 Child2 的 state,因而只有 Child2 触发了一次更新的生命周期。

(2)SignalCenter(信号中心)
神奇的 SignalCenter 究竟是怎样的一回事呢?
const signalCenter = {
onObj: {},
oneObj: {},
subscribe(key, fn) {
if (this.onObj[key] === undefined) this.onObj[key] = [];
this.onObj[key].push(fn);
},
one(key, fn) {
if (this.oneObj[key] === undefined) this.oneObj[key] = [];
this.oneObj[key].push(fn);
},
off(key) {
this.onObj[key] = [];
this.oneObj[key] = [];
},
publish() {
let key, args;
if (arguments.length === 0) return false;
key = arguments[0];
args = [].concat(Array.prototype.slice.call(arguments, 1));
if (this.onObj[key] !== undefined &&
this.onObj[key].length > 0) {
for (let i in this.onObj[key]) {
this.onObj[key][i].apply(null, args);
}
}
if (this.oneObj[key] !== undefined &&
this.oneObj[key].length > 0) {
for (let i in this.oneObj[key]) {
this.oneObj[key][i].apply(null, args);
this.oneObj[key][i] = undefined;
}
this.oneObj[key] = [];
}
}
};
export default signalCenter;
- subscribe、one:subscribe 与 one 函数用于订阅者监听相应的事件,并将事件响应时的函数作为参数,subscribe 与 one 的唯一区别就是,使用 one 进行订阅的函数,只会触发一次,而 使用 subscribe 进行订阅的函数,每次事件发生相应时都会被触发。
- publish:publish 用于发布者发布事件,将除第一参数(事件名)的其他参数,作为新的参数,触发使用 one 与 subscribe 进行订阅的函数。
- off:用于解除所有订阅了某个事件的所有函数。
(3)嵌套较深的组件间通讯
如 Parent>Child1>Child1_1 这种关系,现在我们来完成 Child1_1 向 Parent 传递数据。
(a)父组件 Parent
import React,{Component} from 'react';
import Child1 from './components/Child1';
// SignalCenter 类似于信号中心
import SignalCenter from './components/SignalCenter';
// 父组件 Parent
class Parent extends Component{
state={
msg:'等待Child1_1组件的数据...'
}
componentDidMount(){
// 监听(订阅)msg事件
SignalCenter.subscribe('msg',(msg)=>{
this.setState({
msg
})
});
}
render(){
return (
<div style={{textAlign:'center'}}>
<h3>Parent component</h3>
<h4 style={{color:'red'}}>Child1_1 传递给 Parent 的数据:{this.state.msg}</h4>
<hr/>
<Child1 />
</div>
);
}
}
export default Parent;
(b)Parent 的子组件 Child1
import React,{Component} from 'react';
import Child1_1 from './Child1_1';
// Parent 组件的子组件 Child1
class Child1 extends Component{
componentDidUpdate(){
console.log('Child1 Update');
}
render() {
return (
<div>
<h3>Child1 component</h3>
<hr/>
<Child1_1 />
</div>
);
}
}
export default Child1;
(c)Child1 的子组件 Child1_1
import React,{Component} from 'react';
// SignalCenter 类似于信号中心
import SignalCenter from '../../SignalCenter';
// Child1 组件的子组件 Child1_1
class Child1_1 extends Component{
state={
child1_1_data:'哎哟,不错哦!'
}
componentDidMount() {
setTimeout(() => {
// 发布msg事件
SignalCenter.publish('msg',this.state.child1_1_data);
}, 3000);
}
componentDidUpdate(){
console.log('Child1_1 Update');
}
render() {
return (
<div>
<h3>Child1_1 component</h3>
<h4 style={{color:'red'}}>Child1_1 组件数据:{this.state.child1_1_data}</h4>
</div>
);
}
}
export default Child1_1;
(d)运行结果

我们在 Parent 组件的 componentDidMount 中订阅了 msg 事件,并在 Child1_1 componentDidMount 中,在 3s 后发布了 msg 事件,Parent 组件对 msg 事件做出相应,更新了自身的 state 。我们可以看到,由于在整个通讯过程中,不管组件嵌套有多深,我们通过这种方式只需要关心谁向谁传递数据(即哪个组件向哪个组件发布信息),省掉了由于嵌套太深导致不断通过this.props传递信息的麻烦。
当然,在这里大家要注意一个问题就是在组件传递数据的过程中一定要”先订阅,再发布“,不然你的代码并不会达到你想要的效果。比如案例(1)兄弟组件之间的通讯,如果你把 Child1 组件当中的 setTimeout 给去掉,你会发现并没有将数据传递过去。因为违背了”先订阅,再发布“。
React组件间的通讯的更多相关文章
- React 组件间通讯
React 组件间通讯 说 React 组件间通讯之前,我们先来讨论一下 React 组件究竟有多少种层级间的关系.假设我们开发的项目是一个纯 React 的项目,那我们项目应该有如下类似的关系: 父 ...
- React 组件间通信介绍
React 组件间通信方式简介 React 组件间通信主要分为以下四种情况: 父组件向子组件通信 子组件向父组件通信 跨级组件之间通信 非嵌套组件间通信 下面对这四种情况分别进行介绍: 父组件向子 ...
- vue 和 react 组件间通信方法对比
vue 和 react 组件间通信方法对比: 通信路径 vue的方法 react的方法 父组件 => 子组件 props(推荐).slot(推荐).this.$refs.this.$childr ...
- react组件间的传值方法
关于react的几个网站: http://react.css88.com/ 小书:http://huziketang.mangojuice.top/books/react/ http://www.re ...
- React组件间通信-sub/pub机制
React生命周期第二个demo演示了兄弟组件的通信,需要通过父组件,比较麻烦:下面介绍sub/pub机制来事项组件间通信. 1.导包 npm i pubsub-js 2.UserSearch.jsx ...
- React 组件间通信 总结
组件间通信 5.1.1. 方式一: 通过props传递 1) 共同的数据放在父组件上, 特有的数据放在自己组件内部(state) 2) 通过props可以传递一般数据和 ...
- react组件间传值详解
一.父子组件间传值 <1>父传子 父组件:
- React 组件间传值
壹 .了解React传值的数据 一. 创建组件的方法 一 . 1 通过function声明的组件特点是: 1)function创建的组件是没有state属性,而state属性决定它是不是有生命周期 ...
- React组件间的通信
1.子组件调用父组件,采用props的方式进行调用和赋值,在父组件中设置相关属性值或者方法,子组件通过props的方式进行属性赋值或者方法调用: 2.父组件调用子组件,采用refs的方式进行调用,需要 ...
随机推荐
- Jdk14 都要出了,Jdk8 的时间处理姿势还不了解一下?
当前时间:2019年10月24日.距离 JDK 14 发布时间(2020年3月17日)还有多少天? // 距离JDK 14 发布还有多少天? LocalDate jdk14 = LocalDate.o ...
- .NET Framework概述
1.NET Framework是为其运行的应用程序提供各种服务的托管执行环境,它包括两个主要组件:(1).公共语言运行时 (CLR),(2)..NET Framework 类库: 2.NET Fram ...
- calendar类-时间处理类
calendar类 calendar类是时间处理类 比如在scala中 //字符串转化日期格式 val df = new SimpleDateFormat("yyyy-MM-dd hh:mm ...
- 设计模式C++描述----10.装饰(Decorator)模式
一. 举例 我之前做过一个文件系统就叫 MyFileSys 吧,后来的话,客户想加入一些附加功能,比如压缩.加密.杀毒之类的操作,这些附加操作没有先后顺序,比如你可以先压缩再加密,也可以先杀毒再压缩, ...
- vue项目中v-for渲染失败
在项目中,v-for渲染列表失败,无报错,数组有数据.上网查,好多说是因为动态绑定class的原因,但是经过几番测试,都无效果. 在经过不断尝试,搜索,终于找到原因所在. 问题原因:在v-for循环中 ...
- Mysql用户管理及权限分配
早上到公司,在服务器上Mysql的数据库里新建了个database,然后本地的系统里用原来连接Mysql账号admin连这个数据库.结果报错了,大概是这样子的: Access denied for u ...
- CSP2019游记
第一轮 Day 0 今天正好学校开运动会,就从开幕式开始翘,申请来机房训练. 早上六点多到了机房. 然后果不其然,运动会又下雨了(祈雨大会).机房冷的一批. 今天中午没有午休时间,去食堂吃个饭就直接来 ...
- CSPS模拟 98
T1 待改 T2 这道题的爆炸充分说明我最近已经颓到一定境界了 考虑到总步数不可能超过n 直接枚举总步数,那么任意时刻对末态的影响就是确定的 T3 两遍最短路,一遍从-1的限制考虑求出允许的最早时间, ...
- 拎壶冲冲冲专业砸各种培训机构饭碗篇----python自学(一)
本人一直从事运维工程师,热爱运维,所以从自学的角度站我还是以python运维为主. 一.python自学,当然少不了从hello world开始,话不多说,直接上手练习 1.这个可以学会 print( ...
- P3067 [USACO12OPEN]平衡的奶牛群(折半暴搜)
暴搜无疑.... 首先考虑纯暴搜...... 考虑每一个数: 选在左边集合 选在右边集合 不选 一共三种情况,用一个数组记录搜到的答案,所以暴搜是3^N的复杂度...直接死亡 于是讲折半暴搜.... ...