翻译:展示组件和容器组件

2,807 阅读5分钟

原文链接:Presentational and Container Components

我找到了一个当我们在写react应用时非常有用的模式。如果你写过一段时间的react,你可能已经发现了它。这篇文站很好的解释了它,但是我想补充一些观点。

如果将组件划分为两类,你会发现组件重用起来更加容易。我把这两类称为ContainerPresentational。也有其他叫法,FatSkinny, SmartDumb, StatefulPure, ScreensComponents等等。这些都不完全相同,但是核心思想是相似的。

我的presentational组件:

  • 关心事物如何展示;
  • 也许会同时包含展示类组件和容器组件,并且通常有一些DOM操作和自己的样式;
  • 通常允许this.props.chidren放在容器里;
  • 对应用的其余部分没有依赖,比如Flux actions或者stores
  • 不会说明数据是如何加载和变化的;
  • 只通过props接受数据和回调函数;
  • 几乎没有自己的state(即使有,也是UI相关的,而不是data相关的);
  • 通常被写作函数式组件除非需要状态、生命周期的钩子、性能优化;
  • 例子:Page, Sidebar, Story, UserInfo, List

我的container组件:

  • 关心事物如何运作;
  • 也许会同时包含展示类组件和容器组件,但是除了一些用来包含元素的div通常不会其他有DOM操作,并且不会包含任何样式;
  • presentational组件或其他container组件提供数据和行为;
  • 通常是有状态的,因为它们往往作为数据源;
  • 通常使用像React Reduxconnect()RelaycreateContainer()Flux UtilsContainer.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组件之间的区别不是一个技术问题,相反,区别在于它们的目的。

相比之下,这里有一些相关(但不同!)技术的区别:

  • 有状态的和无状态的。一些组件使用 ReactsetState()方法但有些不使用,当presentational组件往往是无状态的,container组件往往是有状态,这不是一个硬性的规则。presentational组件可以是有状态的,container组件也可以是无状态的。
  • 类和函数React0.14开始,组件可以以函数或类的方式声明。函数式组件定义更简单,但是缺乏一些类特有的功能。这些限制也许在未来会解决但是如今确实存在。由于函数式组件更容易理解,所以我建议你使用函数式组件,除非你需要状态、生命周期钩子和性能优化这些目前只提供给类的功能。
  • 纯和不纯。如果给一个组件输入相同的stateprops,保证会输出相同的结果,大家称这样的组件为纯组件。纯组件可以使用函数或类来声明,并且可以是有状态的或无状态的。纯组件另一个重要的方面是它们不依赖propsstate的深度变化,所以它们的渲染性能可以在shouldComponentUpdate()钩子函数里通过浅比较进行优化。目前只支持在类中定义shouldComponentUpdate(),但是未来也许会改变。

presentational组件和container组件都可以是任意一种状态。以我的经验,presentational组件往往是无状态的纯函数(pure functions),container组件往往是有状态的纯类(pure classes),然而这只是观察得来的结论而不是一种规则,我曾经也看到过完全相反但是合理的情况。

不要把分离presentational组件和container组件当做一种教条。有时候并没那么重要或很难划清界限,如果你不确定一个组件该是presentational组件或container组件,也许是决定的太早了。别担心!