[React Native]组件之间通信

741 查看

本文是一篇译文,英文好的同学可以阅读这里

两个React组件之间如何通信?这是一个很好的问题,有很多种答案。这个取决于组件之间的关系,还有,取决于你更喜欢用哪种方式。

我在这里讲的不是使用数据存储方式在组件之间通信,我真的只是仅仅在谈如何在组件之间通信。

组件之间有三种可能的关系:

-> 表示谁向谁发送消息,例如A->B表示A发送消息给B

  • 主->从(父->子)
  • 从->主(子->父)
  • 两者之间没有任何关联

父-子之间的通信方式:

这个是一种最简单的情况,在React中很自然的一种使用方式并且你正在使用。
你有一个组件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方法生成一个<ToggleButton>,传递一个checked属性。这就是简单的通信。

你只需要给父组件正在rendering的子组件传递一个属性prop就可以了进行通信了。

顺便提一下,在这里例子中,注意一下点击checkbox 是没有效果的。

<ToggleButton>有一个<MyContainer>传递给它的属性checked,但它是没法改变这个属性的(属性checked是不可修改的)。

子-父之间的通信方式:

现在,让我们来讨论下<ToggleButton>控制自己的状态并且想告诉父组件<MyContainer>我被点击了,通知父组件展示一些东西。所以,我们添加一个初始化状态和一个<ToggleButton>点击的事件:

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。因为checkbox只会通知你它的状态改变了,但是不会告诉你它现在是选中还是没有选中。

所以我们在下面这个方法中增加了一行状态改变的代码,和一个待实现的通知父组件<MyContainer>的代码

 onTextChanged: function() {
    this.setState({ checked: !this.state.checked });
    // callbackParent(); // ??
 },

要拿到一个指向父组件的回调对象,我们准备使用在第一种关系父-子之间的通信方式中用到的通信方式。父组件<MyContainer>会通过prop传递一个回调给子组件<ToggleButton>:其实我们可以传递任何对象(属性、方法),他们不是DOM属性,他们只是简单的Javascript对象。

下面是一个例子,演示子组件<ToggleButton>如何通知父组件<MyContainer>它的状态发生了变化。父组件收到这个“变化事件”并且也改变它自己的状态来显示收到的变化:

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

当我点击选择框,父组件收到事件,页面显示为’yes‘.

以上的两种关系都存在一个问题,如果父-子组件之间有好几层其他的组件,那么我们就需要通过props一层层的传递我们的属性和方法,这将是一个灾难性的场景。

没有任何关系的组件之间通信方式:

如果你的组件之间没有任何关联(或者有关联但是两者之间的隔着很多的组件并且你不想通过“中间的组件”去传递属性和方法),那么唯一的方式就是通过某些“事件”,一个组件去订阅这个事件,另外一个组件去触发这个事件。这是任何事件驱动的系统中最基本的两种操作:订阅或者监听一个事件,发送/触发/发布/调度这个事件去通知哪些订阅者。

下面介绍三种模式来处理事件,你可以点击这里比较它们。

  • Event Emitter/Target/Dispatcher :
    需要从EventEmitter/EventTarget/EventDispatcher继承或者实现合适的接口

    myObject.addEventListener('myCustomEventTypeString', handler);
    myObject.dispatchEvent(new Event('myCustomEventTypeString'));
    myObject.removeEventListener('myCustomEventTypeString', handler);

    一个非常简单的EventEmitter,如果没有过多的要求,它可以简单的这么写:

    // 继承EventEmitter 去使用 this.subscribe 和 this.dispatch 这两个方法
    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' });

    这是一个非常简单的EventEmitter ,但是它可以完成我们的需求。

    当然你也可以使用Node.js中的EventEmitter,如果有不清楚的同学,请移步这里

  • Signals
    Event Emitter/Target/Dispatcher 相比,不需要指定事件的“名称”,可以避免硬编码带来的问题

    myObject.myCustomEventType.add(handler);
    myObject.myCustomEventType.dispatch(param1, param2, ...); 
    myObject.myCustomEventType.remove(handler);

    React 团队使用的是: js-signals 它基于 Signals 模式,用起来相当不错。

    这里简单介绍下使用方式
    第一步:命令行切换到你的项目目录,执行

    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 : 类似于很多语言中的事件总线EventBus广播的方式,相比EventEmitter,优点是组件之间可以完全独立,没有任何关联。React中比较常用的是库是PubSubJS,关于这个库的详细使用请查看官方的说明

    这里简单介绍下使用方式

    第一步:命令行切换到你的项目目录,执行

    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 );

    第四步:发布事件

    // 异步发布事件,非阻塞,无需等待订阅者处理结束
    PubSub.publish( 'MY TOPIC', 'hello world!' );
    // 同步发布事件,阻塞,需要等待订阅者处理结束
    PubSub.publishSync( 'MY TOPIC', 'hello world!' );