今天羣裏面有不少都在問關於 React 組件之間是如何通訊的問題,以前本身寫的時候也遇到過這類問題。下面是我看到的一篇不錯英文版的翻譯,看過我博客的人都知道,我翻譯可能不會循序漸進,會盡量用中文的意思,來將做者要講述的技術描述清楚。英文能力有限,若是有不對的地方請跟我留言,必定修改……^_^html
處理 React 組件之間的交流方式,主要取決於組件之間的關係,然而這些關係的約定人就是你。node
我不會講太多關於 data-stores、data-adapters 或者 data-helpers 之類的話題。我下面只專一於 React 組件自己的交流方式的講解。react
React 組件之間交流的方式,能夠分爲如下 3 種:git
初步使用
這個是至關容易的,在使用 React 開發的過程當中常常會使用到,主要是利用 props 來進行交流。例子以下:es6
// 父組件 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 () { // 從【父組件】獲取的值 var checked = this.props.checked, text = this.props.text; return ( <label>{text}: <input type="checkbox" checked={checked} /></label> ); } });
進一步討論
若是組件嵌套層次太深,那麼從外到內組件的交流成本就變得很高,經過 props 傳遞值的優點就不那麼明顯了。(PS:因此我建議儘量的減小組件的層次,就像寫 HTML 同樣,簡單清晰的結構更惹人愛)github
// 父組件 var MyContainer = React.createClass({ render: function() { return ( <Intermediate text="where is my son?" /> ); } }); // 子組件1:中間嵌套的組件 var Intermediate = React.createClass({ render: function () { return ( <Child text={this.props.text} /> ); } }); // 子組件2:子組件1的子組件 var Child = React.createClass({ render: function () { return ( <span>{this.props.text}</span> ); } });
接下來,咱們介紹【子組件】控制本身的 state 而後告訴【父組件】的點擊狀態,而後在【父組件】中展現出來。所以,咱們添加一個 change 事件來作交互。數組
// 父組件 var MyContainer = React.createClass({ getInitialState: function () { return { checked: false }; }, onChildChanged: function (newState) { this.setState({ checked: newState }); }, render: function() { var isChecked = this.state.checked ? 'yes' : 'no'; return ( <div> <div>Are you checked: {isChecked}</div> <ToggleButton text="Toggle me" initialChecked={this.state.checked} callbackParent={this.onChildChanged} /> </div> ); } }); // 子組件 var ToggleButton = React.createClass({ getInitialState: function () { return { checked: this.props.initialChecked }; }, onTextChange: function () { var newState = !this.state.checked; this.setState({ checked: newState }); // 這裏要注意:setState 是一個異步方法,因此須要操做緩存的當前值 this.props.callbackParent(newState); }, render: function () { // 從【父組件】獲取的值 var text = this.props.text; // 組件自身的狀態數據 var checked = this.state.checked; return ( <label>{text}: <input type="checkbox" checked={checked} onChange={this.onTextChange} /></label> ); } });
我以爲原文做者用代碼不是很直觀,接下來我話一個流程走向簡圖來直觀描述一下這個過程:緩存
這樣作實際上是依賴 props 來傳遞事件的引用,並經過回調的方式來實現的,這樣實現不是特別好,可是在沒有任何工具的狀況下也是一種簡單的實現方式
這裏會出現一個咱們在以前討論的問題,就是組件有多層嵌套的狀況下,你必需要一次傳入回調函數給 props 來實現子組件向父組件傳值或者操做。併發
在 onChange 事件或者其餘 React 事件中,你可以獲取如下東西:異步
React 對全部事件的管理都是本身實現的,與咱們以前使用的 onclick、onchange 事件不同。從根本上來講,他們都是綁定到 body 上。
document.on('change', 'input[data-reactid=".0.2"]', function () {...});
上面這份代碼不是來自於 React,只是打一個比方而已。
若是我沒有猜錯的話,React 真正處理一個事件的代碼以下:
var listenTo = ReactBrowserEventEmitter.listenTo; ... function putListener(id, registrationName, listener, transaction) { ... var container = ReactMount.findReactContainerForID(id); if (container) { var doc = container.nodeType === ELEMENT_NODE_TYPE ? container.ownerDocument : container; listenTo(registrationName, doc); } ... } // 在監聽事件的內部,咱們能發現以下: target.addEventListener(eventType, callback, false);
這裏有全部 React 支持的事件:中文文檔-事件系統
多個子組件使用同一個回調的狀況
// 父組件 var MyContainer = React.createClass({ getInitialState: function () { return { totalChecked: 0 }; }, onChildChanged: function (newState) { var newToral = this.state.totalChecked + (newState ? 1 : -1); this.setState({ totalChecked: newToral }); }, render: function() { var totalChecked = this.state.totalChecked; return ( <div> <div>How many are checked: {totalChecked}</div> <ToggleButton text="Toggle me" initialChecked={false} callbackParent={this.onChildChanged} /> <ToggleButton text="Toggle me too" initialChecked={false} callbackParent={this.onChildChanged} /> <ToggleButton text="And me" initialChecked={false} callbackParent={this.onChildChanged} /> </div> ); } }); // 子組件 var ToggleButton = React.createClass({ getInitialState: function () { return { checked: this.props.initialChecked }; }, onTextChange: function () { var newState = !this.state.checked; this.setState({ checked: newState }); // 這裏要注意:setState 是一個異步方法,因此須要操做緩存的當前值 this.props.callbackParent(newState); }, render: function () { // 從【父組件】獲取的值 var text = this.props.text; // 組件自身的狀態數據 var checked = this.state.checked; return ( <label>{text}: <input type="checkbox" checked={checked} onChange={this.onTextChange} /></label> ); } });
這是很是容易理解的,在父組件中咱們增長了一個【totalChecked】來替代以前例子中的【checked】,當子組件改變的時候,使用同一個子組件的回調函數給父組件返回值。
若是組件之間沒有任何關係,組件嵌套層次比較深(我的認爲 2 層以上已經算深了),或者你爲了一些組件可以訂閱、寫入一些信號,不想讓組件之間插入一個組件,讓兩個組件處於獨立的關係。對於事件系統,這裏有 2 個基本操做步驟:訂閱(subscribe)/監聽(listen)一個事件通知,併發送(send)/觸發(trigger)/發佈(publish)/發送(dispatch)一個事件通知那些想要的組件。
下面講介紹 3 種模式來處理事件,你能點擊這裏來比較一下它們。
簡單總結一下:
(1) Event Emitter/Target/Dispatcher
特色:須要一個指定的訂閱源
// to subscribe otherObject.addEventListener(‘click’, function() { alert(‘click!’); }); // to dispatch this.dispatchEvent(‘click’);
(2) Publish / Subscribe
特色:觸發事件的時候,你不須要指定一個特定的源,由於它是使用一個全局對象來處理事件(其實就是一個全局
廣播的方式來處理事件)
// to subscribe globalBroadcaster.subscribe(‘click’, function() { alert(‘click!’); }); // to dispatch globalBroadcaster.publish(‘click’);
(3) Signals
特色:與Event Emitter/Target/Dispatcher類似,可是你不要使用隨機的字符串做爲事件觸發的引用。觸發事件的每個對象都須要一個確切的名字(就是相似硬編碼類的去寫事件名字),而且在觸發的時候,也必需要指定確切的事件。(看例子吧,很好理解)
// to subscribe otherObject.clicked.add(function() { alert(‘click’); }); // to dispatch this.clicked.dispatch();
若是你只想簡單的使用一下,並不須要其餘操做,能夠用簡單的方式來實現:
// 簡單實現了一下 subscribe 和 dispatch var EventEmitter = { _events: {}, dispatch: function (event, data) { if (!this._events[event]) { // 沒有監聽事件 return; } 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] = []; } this._events[event].push(callback); } }; otherObject.subscribe('namechanged', function(data) { alert(data.name); }); this.dispatch('namechanged', { name: 'John' });
若是你想使用 Publish/Subscribe 模型,可使用:PubSubJS
React 團隊使用的是:js-signals 它基於 Signals 模式,用起來至關不錯。
使用 React 事件的時候,必須關注下面兩個方法:
componentDidMount componentWillUnmount
在處理事件的時候,須要注意:
在 componentDidMount 事件中,若是組件掛載(mounted)完成,再訂閱事件;當組件卸載(unmounted)的時候,在 componentWillUnmount 事件中取消事件的訂閱。
(若是不是很清楚能夠查閱 React 對生命週期介紹的文檔,裏面也有描述。原文中介紹的是 componentWillMount 我的認爲應該是掛載完成後訂閱事件,好比Animation這個就必須掛載,而且不能動態的添加,謹慎點更好)
由於組件的渲染和銷燬是由 React 來控制的,咱們不知道怎麼引用他們,因此EventEmitter 模式在處理組件的時候用處不大。
pub/sub 模式可使用,你不須要知道引用。
下面來一個例子:實現有多個 product 組件,點擊他們的時候,展現 product 的名字。
(我在例子中引入了以前推薦的 PubSubJS 庫,若是你以爲引入代價太大,也能夠手寫一個簡版,仍是比較容易的,很好用哈,你們也能夠體驗,可是我仍是不推薦全局廣播的方式)
// 定義一個容器 var ProductList = React.createClass({ render: function () { return ( <div> <ProductSelection /> <Product name="product 1" /> <Product name="product 2" /> <Product name="product 3" /> </div> ); } }); // 用於展現點擊的產品信息容器 var ProductSelection = React.createClass({ getInitialState: function() { return { selection: 'none' }; }, componentDidMount: function () { this.pubsub_token = PubSub.subscribe('products', function (topic, product) { this.setState({ selection: product }); }.bind(this)); }, componentWillUnmount: function () { PubSub.unsubscribe(this.pubsub_token); }, render: function () { return ( <p>You have selected the product : {this.state.selection}</p> ); } }); var Product = React.createClass({ onclick: function () { PubSub.publish('products', this.props.name); }, render: function() { return <div onClick={this.onclick}>{this.props.name}</div>; } });
ES6 中有一種傳遞信息的方式,使用生成函數(generators)和 yield 關鍵字。能夠看一下 https://github.com/ubolonton/js-csp
(這裏我寫一個簡單的 DEMO 介紹一下這種新的傳遞方式,其實大同小異)
function* list() { for(var i = 0; i < arguments.length; i++) { yield arguments[i]; } return "done."; } var o = list(1, 2, 3); var cur = o.next; while(!cur.done) { cur = o.next(); console.log(cur); }
以上例子來自於屈屈的一篇博客:ES6 中的生成器函數介紹 屈屈是一個大牛,你們能夠常常關注他的博客。
一般來講,你有一個隊列,對象在裏面都能找到一個引用,在定義的時候鎖住,當發生的時候,當即打開鎖執行。js-csp 是一種解決辦法,也許之後還會有其餘解決辦法。
在實際應用中,按照實際要解決的需求選擇解決辦法。對於小應用程序,你可使用 props 和回調的方法進行組件之間的數據交換。你能夠經過 pub/sub 模式,以免污染你的組件。在這裏,咱們不是在談論數據,只是組件。對於數據的請求、數據的變化等場景,可使用 Facebook 的 Flux、Relay、GraphQL 來處理,都很是的好用。
文中的每個例子我都驗證過了,主要使用最原始的引入文件方式,建立服務使用的 http-server 包,你們也能夠嘗試本身來一次。
譯:英文原版