最近使用 React Hook 寫項(xiàng)目的新需求,遇到一個(gè)問題就是跨級組件的通信,項(xiàng)目中的組件狀態(tài)沒有用 Redux 來管理狀態(tài),跨級組件通信是通過 React context 來做,如果是 class 組件,可以直接使用 this.context... ,但是現(xiàn)在用 React Hook 來編寫函數(shù)組件,讓組件內(nèi)部有自己的狀態(tài),但是函數(shù)組件是沒有 this 概念的,所以不能用 context 來做,于是想到了 Node.js 中的 events 來實(shí)現(xiàn)。
本來想通過安裝 npm 包里面的 events 來做,然后看到如下這句話
You usually do not have to install
eventsyourself! If your code runs in Node.js,eventsis built in. If your code runs in the browser, bundlers like browserify or webpack also include theeventsmodule.
意思是通過 webpack 打包構(gòu)建的項(xiàng)目,無需單獨(dú)安裝events模塊, webpack 構(gòu)建時(shí)直接包含了 events模塊,所以就直接使用吧。
events 通過發(fā)布訂閱自定義事件來實(shí)現(xiàn)消息的傳播,項(xiàng)目根組件內(nèi)部嵌套很深的子組件在獲取接口請求的時(shí)候,需要將 loaddig 的狀態(tài)傳給根組件,根組件來控制 loadding 動畫的顯示和隱藏,根組件結(jié)構(gòu)如下:
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 動畫加載效果,嵌套組件為了傳遞狀態(tài)給根組件,就需要根組件在掛載之后聲明自定義事件,嵌套組件去訂閱事件,然后通過觸發(fā)自定義事件將狀態(tài)傳遞給根組件。
新建一個(gè) 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)=>{
//這里加定時(shí)是為了保證state.requestCount是最新狀態(tài)
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(){
// 卸載時(shí)移除事件
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,卸載時(shí)在componentWillUnMount中移除事件監(jiān)聽。
嵌套組件 Domain 的代碼如下:
//Domain.js
import PPEmitter from './event'
const Domain = (props) => {
//數(shù)據(jù)請求
const getData = () => {
// 發(fā)起請求顯示 loadding
PPEmitter.emit('changeRequestCount', 1);
setTimeout(() => {
// 數(shù)據(jù)返回之后,隱藏loadding
// 觸發(fā)自定義事件
PPEmitter.emit('changeRequestCount', -1);
}, 1000)
}
}
export default Domain;
效果如下:
