原文地址-欢迎来到我的博客在我们的react项目的日常开发中,经常会遇到这样一个问题:如何实现跨组件通信?为了更好的理解这个问题,我们通过一个简单的栗子来说明一下。实现视频播放器假设有这样的需求,我们需要实现一个简单的视频播放器。基于对播放器的理解,我们大致可以将这个视频播放器分为以下几个部分:视频窗口组件屏幕底部播放控件BottomCtrl对于视频窗口组件,它包含一个播放/暂停按钮而底部的播放控件由以下组件组成:播放/暂停按钮BottomPlayBtn进度控制条ProgressCtrl音量按钮音量所以它的组成应该是如下图所示:同样,我们的组件组织也应该是这样的:(代码这里简化了实现)classMyVideo{render(){return(
)}}//底部视频控件classBottomCtrl{render(){return(
)}}//视频窗口组件类Screen{render(){return(
)}}对于视频播放器,有一个很常见的交互,就是当我们点击屏幕中央的播放按钮CenterPlayBtn时,不仅需要改变自身状态(隐藏),还要更新底部播放按钮BottomPlayBtn的样式.由于中心播放按钮和底部控制按钮分别属于Screen和BottomCtrl组件,所以这是一个非常常见的跨组件通信问题:如何将CenterPlayBtnState同步集成到BottomPlayBtn?方案一:祖先组件的状态管理一种很常见的方式是让祖先组件通过状态管理向其他子组件同步信息:类MyVideo{构造函数(道具){超级(道具);this.state={isPlay:false,}}updatePlayState=isPlay=>{this.setState({isPlay});}render(){const{isPlay}=这个。状态;return(
)}}我们传入祖先的组件的state定义了相应的状态,并将修改状态的方法传递给子组件,那么当一个子组件调用updatePlayState时,它设置的新状态也可以通过react自身的状态更新机制传递给其他子组件,实现跨组件通信的方案虽然简单,但是在一些复杂的场景下还是不够友好:state和methods需要通过props层层传递给相应的子组件。一旦组件嵌套太深,编写和Maintenance沟通困难,中间传递的组件,增加了不必要的逻辑;管理状态的祖先组件将变得更加臃肿。试想一下,假设我们需要为祖先组件添加额外的状态和方法,以实现两个深度嵌套的子组件之间的通信,这增加了祖先组件的维护成本。方案二:redux提供的跨组件通信能力。熟悉redux的童鞋都知道,redux提供的订阅发布机制可以让我们实现任意两个组件之间的通信:首先我们需要给state添加一个key,两者之间需要通过对connect的封装进行通信组件,可以订阅键值的变化。//CenterPlayBtnclassCenterPlayBtn{play(){this.props.updatePlayStatus();}}constmapDispatchToProps=dispatch=>{return{updatePlayStatus:isPlay=>{dispatch(updatePlayStatus(isPlay))}}}exportdefaultconnect(null,mapDispatchToProps)(BottomPlayBtn)classBottomPlayBtn{componentWillReceiveProps(nextProps){if(this.props.isPlay!==nextProps.isPlay){//dosomething}}}constmapStateToProps=state=>({isPlay:state.isPlay})exportdefaultconnect(mapStateToProps,null)(BottomPlayBtn)这是一种很常见的方式使用redux实现跨组件通信,在项目开发中经常使用。那么问题又来了,因为使用这个方案的前提是项目中必须加入redux。如果我的项目比较简单,不需要使用redux,为了实现两个组件之间的简单通信,是否有必要使用redux?一系列的redux配置是否有效?这显然把一个简单的问题复杂化了。方案三:EventEmitterEventEmitter也可以实现跨组件通信。当然这种基于事件订阅的设计模式本身和react关系不大,但是当我们的项目比较小的时候,使用EventEmitter也是一种简单高效的方式:事件。on('pause',()=>{//做某事})}play(){event.发出('播放');}}classBottomPlayBtn{constructor(props){super(props);event.on('play',()=>{//做某事})}pause(){event.emit('pause');}}当然这个方案也有缺陷:组织过于离散。发送者emit和接收者on分散在各个组件中。如果我们不仔细查看每个组件的代码,我们就很难从整体上观察、跟踪和管理这些事件;有可能错过某个事件。如果组件订阅事件的时间太晚,将无法接收到发布者之前发布的事件,而方案1和方案2的好处是,无论怎样,组件都可以获得key的最终状态价值;存在内存泄漏的风险。如果组件被销毁,没有及时取消订阅,则存在内存泄漏的风险;方案四:利用react的nativecontext实现跨组件通信nativereact提供了context,其原文描述如下:context提供了一种通过组件树传递数据的方式,而不必在每一层都手动向下传递props。简单来说,React为你提供了一种跨多层嵌套组件访问数据的方式,而无需手动将props逐一传递下去。这样,我们也可以实现跨组件通信。这个方案和方案一很相似,不同的是我们不需要手动给每一个经过的中层组件传递props。更具体的用法,可以直接参考官网示例。下面只是介绍,给出一个简单的例子:首先我们定义一个player-context.js文件import{createContext}from'react';constPlayerContext=createContext();exportdefaultPlayerContext;然后使用PlayerContext.Provider在MyVideo组件中:importPlayerContextfrom'./player-context';classMyVideo{constructor(props){super(props);this.state={isPlay:false,updatePlayState:this.updatePlayState,}}updatePlayState=isPlay=>{this.setState({isPlay});}render(){return(
)}}然后在需要消费数据的CenterPlayBtn和BottomPlayBtn中使用,这里只是CenterPlayBtn的一个例子:importPlayerContextfrom'./player-context';classCenterPlayBtn{constructor(props){super(props);}play(){this.props.updatePlayStatus(!this.props.isPlay);}componentWillReceiveProps(nextProps){if(this.props.isPlay!==nextProps.isPlay){//做点什么...}}}exportdefaultprops=>({({isPlay,updatePlayStatus})=>})其实我个人认为这个方案是方案一的“加强版”:首先总之,和方案一一样,集中控制和管理数据,即把提供数据内容和修改数据的能力集中给上层组件,让上层组件成为唯一的Provider,供上层组件使用下层无处不在的消费者;其次,它不需要像方案一那样繁琐地手动传递props;一般来说,如果你的项目不使用redux,使用context是一个不错的选择要确定哪种解决方案是最好的,真正重要的是要学会分析哪种解决方案在哪种场景下更好。btw,其实跨组件通信的方式有很多,远不止这些。本人知之甚少,在此只能罗列一些常用的解决方案。我希望这篇文章能启发更好的解决方案和见解:)