今天群里面有不少都在问关于 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 包,你们也能够尝试本身来一次。
译:英文原版