本文是一篇譯文,英文好的同學(xué)可以閱讀這里。
兩個(gè)React組件之間如何通信?這是一個(gè)很好的問題,有很多種答案。這個(gè)取決于組件之間的關(guān)系,還有,取決于你更喜歡用哪種方式。
我在這里講的不是使用數(shù)據(jù)存儲(chǔ)方式在組件之間通信,我真的只是僅僅在談如何在組件之間通信。
組件之間有三種可能的關(guān)系:#####
-> 表示誰(shuí)向誰(shuí)發(fā)送消息,例如A->B表示A發(fā)送消息給B
- 主->從(父->子)
- 從->主(子->父)
- 兩者之間沒有任何關(guān)聯(lián)
父-子之間的通信方式:###
這個(gè)是一種最簡(jiǎn)單的情況,在React中很自然的一種使用方式并且你正在使用。
你有一個(gè)組件A需要和組件B通信,并且給它傳遞一些屬性props。
var MyContainer = React.createClass({
getInitialState: function() {
return { checked: true };
},
render: function() {
return <ToggleButton text="Toggle me" checked={this.state.checked}/>;
}
});
var ToggleButton = React.createClass({
render: function() {
return <label>{this.props.text}: <input type="checkbox" checked={this.props.checked} /></label>;
}
});
這里<MyContainer> 的render方法生成一個(gè)<ToggleButton>,傳遞一個(gè)checked屬性。這就是簡(jiǎn)單的通信。
你只需要給父組件正在rendering的子組件傳遞一個(gè)屬性prop就可以了進(jìn)行通信了。
順便提一下,在這里例子中,注意一下點(diǎn)擊checkbox 是沒有效果的。
<ToggleButton>有一個(gè)<MyContainer>傳遞給它的屬性checked,但它是沒法改變這個(gè)屬性的(屬性checked是不可修改的)。
子-父之間的通信方式:###
現(xiàn)在,讓我們來討論下<ToggleButton>控制自己的狀態(tài)并且想告訴父組件<MyContainer>我被點(diǎn)擊了,通知父組件展示一些東西。所以,我們添加一個(gè)初始化狀態(tài)和一個(gè)<ToggleButton>點(diǎn)擊的事件:
var ToggleButton = React.createClass({
getInitialState: function() {
return { checked: true };
},
onTextChanged: function() {
console.log(this.state.checked); // it is ALWAYS true
},
render: function() {
return <label>{this.props.text}: <input type="checkbox" checked={this.state.checked} onChange={this.onTextChanged}/></label>;
}
});
注意一下我在onTextChanged方法中沒有改變屬性checked的值,它的值一致都是true。因?yàn)?strong>checkbox只會(huì)通知你它的狀態(tài)改變了,但是不會(huì)告訴你它現(xiàn)在是選中還是沒有選中。
所以我們?cè)谙旅孢@個(gè)方法中增加了一行狀態(tài)改變的代碼,和一個(gè)待實(shí)現(xiàn)的通知父組件<MyContainer>的代碼
onTextChanged: function() {
this.setState({ checked: !this.state.checked });
// callbackParent(); // ??
},
要拿到一個(gè)指向父組件的回調(diào)對(duì)象,我們準(zhǔn)備使用在第一種關(guān)系父-子之間的通信方式中用到的通信方式。父組件<MyContainer>會(huì)通過prop傳遞一個(gè)回調(diào)給子組件<ToggleButton>:其實(shí)我們可以傳遞任何對(duì)象(屬性、方法),他們不是DOM屬性,他們只是簡(jiǎn)單的Javascript對(duì)象。
下面是一個(gè)例子,演示子組件<ToggleButton>如何通知父組件<MyContainer>它的狀態(tài)發(fā)生了變化。父組件收到這個(gè)“變化事件”并且也改變它自己的狀態(tài)來顯示收到的變化:
var MyContainer = React.createClass({
getInitialState: function() {
return { checked: false };
},
onChildChanged: function(newState) {
this.setState({ checked: newState });
},
render: function() {
return <div>
<div>Are you checked ? {this.state.checked ? 'yes' : 'no'}</div>
<ToggleButton text="Toggle me" initialChecked={this.state.checked} callbackParent={this.onChildChanged} />
</div>;
}
});
var ToggleButton = React.createClass({
getInitialState: function() {
// we ONLY set the initial state from the props
return { checked: this.props.initialChecked };
},
onTextChanged: function() {
var newState = !this.state.checked;
this.setState({ checked: newState });
this.props.callbackParent(newState); // hey parent, I've changed!
},
render: function() {
return <label>{this.props.text}: <input type="checkbox" checked={this.state.checked} onChange={this.onTextChanged}/></label>;
}
});
這里是最終的效果:

當(dāng)我點(diǎn)擊選擇框,父組件收到事件,頁(yè)面顯示為’yes‘.
以上的兩種關(guān)系都存在一個(gè)問題,如果父-子組件之間有好幾層其他的組件,那么我們就需要通過props一層層的傳遞我們的屬性和方法,這將是一個(gè)災(zāi)難性的場(chǎng)景。
沒有任何關(guān)系的組件之間通信方式:###
如果你的組件之間沒有任何關(guān)聯(lián)(或者有關(guān)聯(lián)但是兩者之間的隔著很多的組件并且你不想通過“中間的組件”去傳遞屬性和方法),那么唯一的方式就是通過某些“事件”,一個(gè)組件去訂閱這個(gè)事件,另外一個(gè)組件去觸發(fā)這個(gè)事件。這是任何事件驅(qū)動(dòng)的系統(tǒng)中最基本的兩種操作:訂閱或者監(jiān)聽一個(gè)事件,發(fā)送/觸發(fā)/發(fā)布/調(diào)度這個(gè)事件去通知哪些訂閱者。
下面介紹三種模式來處理事件,你可以點(diǎn)擊這里比較它們。
-
Event Emitter/Target/Dispatcher :
需要從EventEmitter/EventTarget/EventDispatcher繼承或者實(shí)現(xiàn)合適的接口myObject.addEventListener('myCustomEventTypeString', handler); myObject.dispatchEvent(new Event('myCustomEventTypeString')); myObject.removeEventListener('myCustomEventTypeString', handler);
一個(gè)非常簡(jiǎn)單的EventEmitter,如果沒有過多的要求,它可以簡(jiǎn)單的這么寫:
// 繼承EventEmitter 去使用 this.subscribe 和 this.dispatch 這兩個(gè)方法
var EventEmitter = {
_events: {},
dispatch: function (event, data) {
if (!this._events[event]) return; // no one is listening to this event
for (var i = 0; i < this._events[event].length; i++)
this._events[event][i](data);
},
subscribe: function (event, callback) {
if (!this._events[event]) this._events[event] = []; // new event
this._events[event].push(callback);
}
}
otherObject.subscribe('namechanged', function(data) { alert(data.name); });
this.dispatch('namechanged', { name: 'John' });
這是一個(gè)非常簡(jiǎn)單的EventEmitter ,但是它可以完成我們的需求。
當(dāng)然你也可以使用Node.js中的EventEmitter,如果有不清楚的同學(xué),請(qǐng)移步這里
-
Signals:
與Event Emitter/Target/Dispatcher 相比,不需要指定事件的“名稱”,可以避免硬編碼帶來的問題myObject.myCustomEventType.add(handler); myObject.myCustomEventType.dispatch(param1, param2, ...); myObject.myCustomEventType.remove(handler);
React 團(tuán)隊(duì)使用的是: js-signals 它基于 Signals 模式,用起來相當(dāng)不錯(cuò)。
這里簡(jiǎn)單介紹下使用方式
第一步:命令行切換到你的項(xiàng)目目錄,執(zhí)行
npm install signals
第二步:引入模塊
const signals= require('signals');
第三步:使用方式
//custom object that dispatch a `started` signal
var myObject = {
started : new signals.Signal()
};
function onStarted(param1, param2){
alert(param1 + param2);
}
myObject.started.add(onStarted); //add listener
myObject.started.dispatch('foo', 'bar'); //dispatch signal passing custom parameters
myObject.started.remove(onStarted); //remove a single listener
-
Publish / Subscribe : 類似于很多語(yǔ)言中的事件總線EventBus廣播的方式,相比EventEmitter,優(yōu)點(diǎn)是組件之間可以完全獨(dú)立,沒有任何關(guān)聯(lián)。React中比較常用的是庫(kù)是PubSubJS,關(guān)于這個(gè)庫(kù)的詳細(xì)使用請(qǐng)查看官方的說明
這里簡(jiǎn)單介紹下使用方式
第一步:命令行切換到你的項(xiàng)目目錄,執(zhí)行
npm install pubsub-js第二步:引入模塊
const PubSub = require('pubsub-js');第三步:訂閱事件
// create a function to subscribe to topics var mySubscriber = function( msg, data ){ console.log( msg, data ); }; // add the function to the list of subscribers for a particular topic // we're keeping the returned token, in order to be able to unsubscribe // from the topic later on var token = PubSub.subscribe( 'MY TOPIC', mySubscriber );第四步:發(fā)布事件
// 異步發(fā)布事件,非阻塞,無需等待訂閱者處理結(jié)束 PubSub.publish( 'MY TOPIC', 'hello world!' ); // 同步發(fā)布事件,阻塞,需要等待訂閱者處理結(jié)束 PubSub.publishSync( 'MY TOPIC', 'hello world!' );