在咱们react项目平常开发中,每每会遇到这样一个问题:如何去实现跨组件通讯?javascript
为了更好的理解此问题,接下来咱们经过一个简单的栗子说明。html
假设有一个这样的需求,须要咱们去实现一个简易的视频播放器,基于对播放器的理解,咱们能够把这个视频播放器大体分为以下几个部分:java
Screen
BottomCtrl
对于视频窗口组件,它包含一个播放/暂停按钮CenterPlayBtn
;而底部播放控件又是由如下几种组件组合而成:react
BottomPlayBtn
ProgressCtrl
Volume
因而乎它的构成应该以下图所示: redux
一样的,咱们的组件组织方式应该也长这样:(这里简化了代码实现)设计模式
class MyVideo {
render() {
return (
<div> <Screen /> <BottomCtrl /> </div>
)
}
}
// 底部视频控件
class BottomCtrl {
render() {
return (
<div> <BottomPlayBtn /> <ProgressCtrl /> <Volume /> </div>
)
}
}
// 视频窗口组件
class Screen {
render() {
return (
<div> <video /> <ScreenPlayBtn /> </div>
)
}
}
复制代码
对于视频播放器而言,有一个很常见的交互,即当咱们点击屏幕中心的播放按钮CenterPlayBtn
时,不只须要改变自身的状态(隐藏起来),并且还要更新底部播放按钮BottomPlayBtn
的样式ide
因为中心播放按钮与底部控件按钮分别属于Screen
、BottomCtrl
组件的部分,所以这就是一个很常见的跨组件通讯问题:如何将CenterPlayBtn
的状态同步到BottomPlayBtn
?this
一个很是经常使用的方式,就是让祖先组件经过状态管理的方式把信息同步到其余子组件中:spa
class MyVideo {
constructor(props) {
super(props);
this.state = {
isPlay: false,
}
}
updatePlayState = isPlay => {
this.setState({ isPlay });
}
render() {
const { isPlay } = this.state;
return (
<div>
<Screen updatePlayState={this.updatePlayState} isPlay={isPlay} />
<BottomCtrl updatePlayState={this.updatePlayState} isPlay={isPlay} />
</div>
)
}
}
复制代码
咱们经过在祖先组件的state定义相应的状态,并把修改state的方法传递给了子组件,那么当一个子组件经过调用updatePlayState
后,它所设置的新状态亦可经过react自己的state更新机制传递给其余的子组件,实现跨组件通讯。设计
这种方案虽然简单,但在一些复杂的场景下却显得不够友好:
熟悉redux的童鞋都知道,redux提供的订阅发布机制,可让咱们实现任何两个组件的通讯:首先咱们须要在state上去添加一个key,在两个须要通讯的组件上经过connect
的封装,便可订阅key值的改变。
// CenterPlayBtn
class CenterPlayBtn {
play() {
this.props.updatePlayStatus();
}
}
const mapDispatchToProps = dispatch => {
return {
updatePlayStatus: isPlay => {
dispatch(updatePlayStatus(isPlay))
}
}
}
export default connect(null, mapDispatchToProps)(BottomPlayBtn)
复制代码
class BottomPlayBtn {
componentWillReceiveProps(nextProps) {
if (this.props.isPlay !== nextProps.isPlay) {
// do something
}
}
}
const mapStateToProps = state => ({
isPlay: state.isPlay
})
export default connect(mapStateToProps, null)(BottomPlayBtn)
复制代码
使用redux的方式去实现跨组件通讯是一种很常见的方式,在项目开发中也常常用到。那问题又来了,因为使用这种方案的前提是必须得在项目中加入redux,若是个人项目原本就比较简单,不须要使用到redux,难道为了实现两个组件简单的通讯而要去作一系列redux的配置工做吗?这显然把简单的问题又复杂化了。
EventEmitter也能够实现跨组件通讯,固然这种基于事件订阅的设计模式自己也与react关系不大,但咱们的项目很小的时候,使用EventEmitter也不失为一种简单且高效的方式:
class CenterPlayBtn {
constructor(props) {
super(props);
event.on('pause', () => {
// do something
})
}
play() {
event.emit('play');
}
}
class BottomPlayBtn {
constructor(props) {
super(props);
event.on('play', () => {
// do something
})
}
pause() {
event.emit('pause');
}
}
复制代码
固然这种方案也是有缺陷的:
emit
与接收者on
分散在各个组件里,若是不细看每一个组件的代码,咱们难以从总体去观察、跟踪、管理这些事件;原生react提供了context,它的原文描述是这样的:
Context provides a way to pass data through the component tree without having to pass props down manually at every level.
简单来讲就是react提供了一种方式,让你能够跨多层嵌套组件去访问数据,而不须要手动的将props一个一个地传递下去。经过这种方式咱们也能够实现跨组件通讯方式,这个方案和方案一很类似,但区别在于咱们无需手动将props传递给经历的每个中间层组件。更为具体的用法能够直接参考官网示例,下面只是抛砖引玉,给出个简单示例:
首先咱们定义一个player-context.js
文件
import { createContext } from 'react';
const PlayerContext = createContext();
export default PlayerContext;
复制代码
而后在MyVideo
组件中使用PlayerContext.Provider
:
import PlayerContext from './player-context';
class MyVideo {
constructor(props) {
super(props);
this.state = {
isPlay: false,
updatePlayState: this.updatePlayState,
}
}
updatePlayState = isPlay => {
this.setState({ isPlay });
}
render() {
return (
<PlayerContext.Provider value={this.state}> <Screen /> <BottomCtrl /> </PlayerContext.Provider> ) } } 复制代码
接着在须要消费数据的地方CenterPlayBtn
和BottomPlayBtn
中使用到它,这里只给出CenterPlayBtn
的示例:
import PlayerContext from './player-context';
class CenterPlayBtn {
constructor(props) {
super(props);
}
play() {
this.props.updatePlayStatus(!this.props.isPlay);
}
componentWillReceiveProps(nextProps) {
if (this.props.isPlay !== nextProps.isPlay) {
// do something...
}
}
}
export default props => (<PlayerContext.Consumer>
{
({isPlay, updatePlayStatus}) => <CenterPlayBtn {...props} isPlay={isPlay} updatePlayStatus={updatePlayStatus} />
}
</PlayerContext.Consumer>)
复制代码
其实我的认为这种方案是方案一的“加强版”:
Provider
,供下层各处的消费者Consumer
使用;props
手动向下传递;总得来讲,若是你的项目没有使用到redux的话,使用context
是个不错的选择。
上面列举的方案各有优劣,咱们很难去断定哪一种方案是最好的,而真正重要的,是要学会分析哪一个场景下使用哪一种方案更佳。
btw,其实跨组件通讯的方式多种多样,远不止这些,本人才疏学浅,这里只能列举出一些本身经常使用的解决方案,但愿此文能抛砖引玉,引出更棒的方案和看法:)