组件间的通信
在上一篇的文章中,我们主要聊了如何去设计一个好的组件。然而那个视角是独立的,甚至说是狭隘的,只考虑了的单个组件自己。接下来我们将考虑更复杂的情况,例如组件间的交互等等。
其实Facebook官方关于React最佳实践已经写的非常好的了,但可惜的是这些实践和工作原理、教程都混杂在一起,并且长篇累牍,让人阅读起来非常没有重点;另一方面如果你是个新手,或许你会看完所有的这些文档,但你还没有实践的经验,你满脑子想的其实是如何开始写你的第一个Hello World应用,所以这些最佳实践你也很快就忘了。
这一小节的主要内容都来自上面所说的Facebook关于React的官方文档,还有一部分来自一篇我认为很有价值,理念和官方文档一致但又写的更好的一篇文章:Flux in Depth. Overview and Components
组件间的通信问题
首先假设我们有一个用React构建的单页面应用,组件间的关系如下图所示:
我们首先假设每一个组件都在维护自己的状态(state),而不是使用传递下来的props。那么问题来了,如果右侧的组件E想改变左侧的组件B的状态,应该怎么办?
这个例子其实想表达的是一类通用的业务场景,就是组件间状态的修改,也有可能是子组件修改父组件的状态。上面的这个例子是一类极端的情况,被修改的组件与发出修改的组件不在同一个父组件中。
Facebook的官方推荐办法(曾经是)是使用事件机制(现在这个页面已经跳转到另一个解决方案了,也就是下面要叙述的)。但我和绝大多数人都不认为这是一个好的机制。你可以想象一旦应用中这样的需求增多,事件和回调函数满天飞,则情况右回到了Flux之前的原地状态:调试和维护代码都很难进行,这会是一个灾难。
第二个方案是传递接口。如果E想改变B的状态,那么B要传递给E组件一个修改状态的接口。不过因为Flux属性是从上至下传递的关系,所以接口的传递应该是如下图所示:
所以事实上我们要从祖先元素Root传递两个接口分别给E和B。当E想改变B的状态时,E调用root传给它的那个接口,而后那个接口又调用root传给B的接口……听上去这也不是什么好办法。
正确的Flux架构
所以目前官方推荐的React组件和Flux架构的最佳实践是:
- 在开发组件和设计组件时保证组件是 Stateless Component。牢记组件的职责只有一个,那就是 渲染数据。
- 在所有组件顶端设置一个相反的 Statefull Component,把所有的数据和状态都置于这个“充满状态的组件”中(类似于我们上一篇讲的Container Component),然后通过属性传递的方式将数据传递给孩子组件
- 充满状态的组件封装交互逻辑并且负责状态管理;而无状态的组件负责渲染数据
这样的原则有没有眼熟?这正是Vuex的架构思想,Vuex架构受Flux启发,同时也针对Flux的一些缺陷做出了改进,在下一篇文章中我会聊到Vuex与Flux的差异。
当然Stateless也不是绝对的,比如一些第三方组件,比如React-DND,就需要维护自己的状态。
state里应该有什么
最后提一句state里应该有什么。这个题目也是有标准答案的,在Interactivity and Dynamic UIs这篇文章里。
- State里应该包含什么:组件的事件处理函数可能进行修改的,导致UI更新的数据(State should contain data that a component’s event handlers may change to trigger a UI update. )
- State里不应该有什么:
- 计算得出的数据
- React组件
- 从props复制来的数据