组件间的通信

在上一篇的文章中,我们主要聊了如何去设计一个好的组件。然而那个视角是独立的,甚至说是狭隘的,只考虑了的单个组件自己。接下来我们将考虑更复杂的情况,例如组件间的交互等等。

其实Facebook官方关于React最佳实践已经写的非常好的了,但可惜的是这些实践和工作原理、教程都混杂在一起,并且长篇累牍,让人阅读起来非常没有重点;另一方面如果你是个新手,或许你会看完所有的这些文档,但你还没有实践的经验,你满脑子想的其实是如何开始写你的第一个Hello World应用,所以这些最佳实践你也很快就忘了。

这一小节的主要内容都来自上面所说的Facebook关于React的官方文档,还有一部分来自一篇我认为很有价值,理念和官方文档一致但又写的更好的一篇文章:Flux in Depth. Overview and Components​

组件间的通信问题

首先假设我们有一个用React构建的单页面应用,组件间的关系如下图所示:

sample state change

我们首先假设每一个组件都在维护自己的状态(state),而不是使用传递下来的props。那么问题来了,如果右侧的组件E想改变左侧的组件B的状态,应该怎么办?

这个例子其实想表达的是一类通用的业务场景,就是组件间状态的修改,也有可能是子组件修改父组件的状态。上面的这个例子是一类极端的情况,被修改的组件与发出修改的组件不在同一个父组件中。

Facebook的官方推荐办法(曾经是)是使用事件机制(现在这个页面已经跳转到另一个解决方案了,也就是下面要叙述的)。但我和绝大多数人都不认为这是一个好的机制。你可以想象一旦应用中这样的需求增多,事件和回调函数满天飞,则情况右回到了Flux之前的原地状态:调试和维护代码都很难进行,这会是一个灾难。

第二个方案是传递接口。如果E想改变B的状态,那么B要传递给E组件一个修改状态的接口。不过因为Flux属性是从上至下传递的关系,所以接口的传递应该是如下图所示:

sample state change by callback

所以事实上我们要从祖先元素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复制来的数据

参考资料

results matching ""

    No results matching ""