[React Native]組件之間通信

本文是一篇譯文,英文好的同學(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>;
  }
});

這里是最終的效果:


圖1

當(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!' );
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容