我找到了一个当我们在写react应用时非常有用的模式。如果你写过一段时间的react,你可能已经发现了它。这篇文站很好的解释了它,但是我想补充一些观点。
如果将组件划分为两类,你会发现组件重用起来更加容易。我把这两类称为Container和Presentational。也有其他叫法,Fat 和 Skinny, Smart 和 Dumb, Stateful 和 Pure, Screens 和 Components等等。这些都不完全相同,但是核心思想是相似的。
我的presentational组件:
- 关心事物如何展示;
- 也许会同时包含展示类组件和容器组件,并且通常有一些DOM操作和自己的样式;
- 通常允许
this.props.chidren放在容器里; - 对应用的其余部分没有依赖,比如
Flux actions或者stores; - 不会说明数据是如何加载和变化的;
- 只通过
props接受数据和回调函数; - 几乎没有自己的
state(即使有,也是UI相关的,而不是data相关的); - 通常被写作函数式组件除非需要状态、生命周期的钩子、性能优化;
- 例子:
Page,Sidebar,Story,UserInfo,List。
我的container组件:
- 关心事物如何运作;
- 也许会同时包含展示类组件和容器组件,但是除了一些用来包含元素的div通常不会其他有DOM操作,并且不会包含任何样式;
- 为
presentational组件或其他container组件提供数据和行为; - 通常是有状态的,因为它们往往作为数据源;
- 通常使用像
React Redux的connect()、Relay的createContainer()、Flux Utils的Container.create()这样的高阶组件来生成,而不是手动编写; - 例子:
UserPage,FollowersSidebar,StoryContainer,FollowedUserList。
这种方式的好处:
- 更好的关注分离。通过这种方式编写组件,你能更好的理解你的应用和试图;
- 更好的可重用性。你可以将相同的
presentational组件和完全不同的状态组合在一起,并将这些转化为可进一步重用的独立的容器组件; presentational组件本质上是你应用的“调色盘”。你可以将它们放在一个单页面,设计师不接触应用的逻辑就可以调整所有的变化;- 这迫使你提取你的布局组件比如
Sidebar,Page,ContextMenu,使用this.props.children而不是在几个容器组件之间复制标签和布局。
什么时候引入Containers?
我建议你一开始只用presentational组件来构建应用。最后你会意识到通过中间组件传递了太多的props。当你注意到一些组件并不使用它们收到的props而几乎只是向下传递,并且当子组件需要更多数据的时候你得重写所有的中间组件,这就是应该引入container组件的好时候了。通过这种方式,你可以通过叶组件得到需要的数据和行为,而不会加重组件树中不相关的组件。
这是一个持续重构的过程,所以不要尝试一次就做的好。当你尝试这种模式,你会形成一种什么时候该提取一个container的直觉,就像你知道什么时候该提取一个函数一样。我的free Redux Egghead series也能在这方面帮助你。
其他分类
重要的是要理解presentational组件和container组件之间的区别不是一个技术问题,相反,区别在于它们的目的。
相比之下,这里有一些相关(但不同!)技术的区别:
- 有状态的和无状态的。一些组件使用
React的setState()方法但有些不使用,当presentational组件往往是无状态的,container组件往往是有状态,这不是一个硬性的规则。presentational组件可以是有状态的,container组件也可以是无状态的。 - 类和函数。从
React0.14开始,组件可以以函数或类的方式声明。函数式组件定义更简单,但是缺乏一些类特有的功能。这些限制也许在未来会解决但是如今确实存在。由于函数式组件更容易理解,所以我建议你使用函数式组件,除非你需要状态、生命周期钩子和性能优化这些目前只提供给类的功能。 - 纯和不纯。如果给一个组件输入相同的
state和props,保证会输出相同的结果,大家称这样的组件为纯组件。纯组件可以使用函数或类来声明,并且可以是有状态的或无状态的。纯组件另一个重要的方面是它们不依赖props和state的深度变化,所以它们的渲染性能可以在shouldComponentUpdate()钩子函数里通过浅比较进行优化。目前只支持在类中定义shouldComponentUpdate(),但是未来也许会改变。
presentational组件和container组件都可以是任意一种状态。以我的经验,presentational组件往往是无状态的纯函数(pure functions),container组件往往是有状态的纯类(pure classes),然而这只是观察得来的结论而不是一种规则,我曾经也看到过完全相反但是合理的情况。
不要把分离presentational组件和container组件当做一种教条。有时候并没那么重要或很难划清界限,如果你不确定一个组件该是presentational组件或container组件,也许是决定的太早了。别担心!