容器组件和展示组件(译)

2020-12-21

本文翻译自Redux作者、React核心开发者 Dan Abramov在2015年所写的博文 Presentational and Container Components,我最近看Redux文档才追过来,其中的理念并不过时,故此记录

在写React应用的时候,我发现一些简单的模式非常有用,如果你写过一段时间的React,那么你应该已经发现了。这有一篇文章阐述了它们,但我想加入一些更多的观点。

如果你把你的组件分为两类的话,你会发现它们很容易被复用和推断。我称它们为容器组件和展示组件,和我之前所听到过的胖和瘦,聪明和笨拙,有无状态,屏幕和组件等等类似,它们虽然都不是十分相同,但核心的思想是相似的。

展示组件:

  • 只关心如何展示
  • 它可以同时包含展示组件和容器组件在内,通常还会有一些DOM元素和样式在内
  • 总是通过this.props.children来构建
  • 不会依赖app的其余部分,像Flux的actions、stores
  • 不需要关心数据是如何被加载和改变的
  • 只通过props来获取数据和回调函数
  • 很少有自己的状态(如果有,它的UI state将会高于data)
  • 写成函数组件,除非它们需要状态,生命周期钩子,或是性能优化
  • 例如:Page, Sidebar, Story, UserInfo, List

容器组件:

  • 只关心操作数据
  • 也可以包含展示组件和容器组件,但它通常不会有DOM元素,除了一些用于作包含用的无样式div
  • 提供数据和行为给其它的展示组件或包含组件
  • 调用Flux的actions和提供它们给展示组件
  • 常常是有状态的,它们倾向于作数据源
  • 通常使用高级的命令组件生成。像React Redux的connect(),Relay的createContainer(),Flux Utils里的Container.create(),而不是手工编写的
  • 例如:UserPage, FollowersSidebar, StoryContainer, FollowedUserList

我会把它们放到不同的文件夹下用于区分

这样区分的好处

  • 更好的关注点分离。通过这种方式你可以更好的理解你的app和UI
  • 更好的可复用性。你可以在相同的展示组件上使用完全不同的数据源,它们会转变为容器组件方便更进一步的复用
  • 本质上,展示组件是你app里的调色盘,你可以把它们放到一个单页面上,然后让设计师来调整它们的变化而不用理会app的逻辑。你也可以在页面上运行屏幕快照回归测试
  • 迫使你提取出"布局组件",像Sidebar, Page, ContextMenu一样。同时使用this.props.children来代替一些重复的标记和布局——在仅有的几个包含组件里

记住,组件不应该触发DOM,它们只需要在UI关注点之间提供构成边界。

好好利用这一点。

什么时候使用容器组件

首先,我建议你仅仅使用展示组件来构建你的app。最后你会意识到已经有太多的props存在于中间组件里。当你意识到一些组件并没有使用它们接收到的props,仅仅是需要把它传递下去,而且在子组件需要时不得不重新连接,这就是使用容器组件的好时机。通过这种方式你可以拿到叶子组件上的数据和方法,而不会给中间组件增加负担。

这就是一个不断重构的过程,不要指望一开始就能做得完美。当你有足够的经验了,在需要提取容器组件时,你就会有这种直觉了,就像你知道什么时候需要提取方法了一样。

其它二分法

你要知道,展示组件和容器组件的区别,并不是技术上的区别,而是它们目的上的区别。

相较之下,这里有一些技术上相似(但不相同)的区别。

  • 有状态的和无状态的。一些组件使用React setState()方法,一些组件没有。尽管容器组件往往是有状态的,展示组件往往没有状态,但这并不是一条严格的规则。容器组件也可以没有状态,展示组件也可以有状态
  • 类组件和函数组件。从React0.14开始,组件可以被定义为一个类或者一个函数。函数式组件相较于于类组件更简单,但是它少了一些类组件才能使用的特性。一些严格的限制未来可能会消失,但是现在还存在。因为函数组件便于理解,我建议你多使用它们,除非你需要状态、生命周期钩子或者要做性能优化,这个时候你应该使用类组件
  • 纯净的和不纯的。如果给一个组件同样的props和state时,它一定返回相同的值,我们称它是纯净的。纯净组件可以被定义为class或者function,也可以有状态或者无状态。纯净组件还有一个很重要的点,它不会依赖props或者state的深度变更,所以它们的渲染是可以通过shouldComponentUpdate()来优化的。当前只有类组件能够定义shouldComponentUpdate(),未来可能会变更。

展示组件和容器组件都可以互相包含。在我的经验里,展示组件趋向于无状态的纯函数,容器组件往往是有状态的纯类。但这也只是一个观察不是一个规则,我也见过很多完全相反的例子,它们在特定情况下也很有意义。

不要让展示组件和容器组件作为一个区分的教条。有时会变得很困难去区分这条线。如果你不确信是否应该划分为展示组件或容器组件,往往太过早做决定了,不必紧张,时机会到的。