最近使用 React Hook 写项目的新需求,遇到一个问题就是跨级组件的通信,项目中的组件状态没有用 Redux 来管理状态,跨级组件通信是通过 React context 来做,如果是 class 组件,可以直接使用 this.context... ,但是现在用 React Hook 来编写函数组件,让组件内部有自己的状态,但是函数组件是没有 this 概念的,所以不能用 context 来做,于是想到了 Node.js 中的 events 来实现。
本来想通过安装 npm 包里面的 events 来做,然后看到如下这句话
You usually do not have to install
events
yourself! If your code runs in Node.js,events
is built in. If your code runs in the browser, bundlers like browserify or webpack also include theevents
module.
意思是通过 webpack 打包构建的项目,无需单独安装events
模块, webpack 构建时直接包含了 events
模块,所以就直接使用吧。
events
通过发布订阅自定义事件来实现消息的传播,项目根组件内部嵌套很深的子组件在获取接口请求的时候,需要将 loaddig 的状态传给根组件,根组件来控制 loadding 动画的显示和隐藏,根组件结构如下:
export default class MainFrame extends React.Component{
render(){
return(
<Spin style={{height:'100%'}} spinning={!!this.state.requestCount}>
<Layout style={{height:'100%',width:'100%'}}>
<MyHeader user={this.state.userInfo}/>
<div className="page-content-warpper table-4-ie9">
<Layout className="table-row-4-ie9">
<Sider className="table-cell-4-ie9" width={200}>
<LeftMenu
mySelect={this.props.menuselect}
menu={this.props.menu}
classNmae="page-left-menu"/>
</Sider>
<Layout className="table-cell-4-ie9" style={{ padding: '10px 20px' }}>
<Content>
{this.props.children}
</Content>
</Layout>
</Layout>
</div>
</Layout>
</Spin>
)
}
}
右边的子组件就在 this.props.children 中显示,Spin
的spinning
就是做全局的 loadding 动画加载效果,嵌套组件为了传递状态给根组件,就需要根组件在挂载之后声明自定义事件,嵌套组件去订阅事件,然后通过触发自定义事件将状态传递给根组件。
新建一个 events.js
文件,代码如下:
// events.js
import EventEmitter from 'events';
class PPEmitter extends EventEmitter {};
export default new PPEmitter();
根组件注册自定义事件,根组件为MainFrame.js
// MainFrame.js
import PPEmitter from './event';
export default class MainFrame extends React.Component{
constructor(props){
super(props);
this.state={
requestCount:0,
}
}
changeRequestCount = (count)=>{
//这里加定时是为了保证state.requestCount是最新状态
setTimeout(()=>{
let requestCount = this.state.requestCount+count;
requestCount = requestCount<0?0:requestCount;
this.setState({requestCount})
})
}
componentDidMount(){
// 声明自定义事件
this.eventEmitter = PPEmitter.addListener('changeRequestCount', (msg) => {
this.changeRequestCount(msg);
})
}
componentWillUnmount(){
// 卸载时移除事件
PPEmitter.removeListener(this.eventEmitter);
}
render(){
return(
<Spin style={{height:'100%'}} spinning={!!this.state.requestCount}>
<Layout style={{height:'100%',width:'100%'}}>
<MyHeader user={this.state.userInfo}/>
<div className="page-content-warpper table-4-ie9">
<Layout className="table-row-4-ie9">
<Sider className="table-cell-4-ie9" width={200}>
<LeftMenu mySelect={this.props.menuselect} menu={this.props.menu} classNmae="page-left-menu"/>
</Sider>
<Layout className="table-cell-4-ie9" style={{ padding: '10px 20px' }}>
<Content>
{this.props.children}
</Content>
</Layout>
</Layout>
</div>
</Layout>
</Spin>
)
}
}
MainFrame 组件在componentDidMount
中注册自定义事件changeRequestCount
,卸载时在componentWillUnMount
中移除事件监听。
嵌套组件 Domain 的代码如下:
//Domain.js
import PPEmitter from './event'
const Domain = (props) => {
//数据请求
const getData = () => {
// 发起请求显示 loadding
PPEmitter.emit('changeRequestCount', 1);
setTimeout(() => {
// 数据返回之后,隐藏loadding
// 触发自定义事件
PPEmitter.emit('changeRequestCount', -1);
}, 1000)
}
}
export default Domain;
效果如下:

网友评论