[React Native]组件之间通信

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

两个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!' );
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,099评论 18 139
  • Module definition patterns 除了作为加载依赖的机制之外,模块系统也是一种用于定义AP...
    宫若石阅读 418评论 0 0
  • 这篇笔记主要包含 Vue 2 不同于 Vue 1 或者特有的内容,还有我对于 Vue 1.0 印象不深的内容。关于...
    云之外阅读 4,989评论 0 29
  • 明天就是清明小长假了,琪妹可以缓一缓。工作都还好,主要是因为临座是一个神一样存在的立本人,经常一股无名火。 咱们经...
    tyouiki阅读 235评论 0 0
  • 上古时候的人们有结绳记事之法,大概一天或者一段时间干了什么,数一数拿绳子打了多少个结,可以说出个大概。 我猜,用不...
    qiaoleon2011阅读 862评论 1 1