网络埋伏纪事

展示性组件和容器组件

网络埋伏纪事 · 2016-11-13翻译 · 1680阅读 原文链接

Bismuth

在编写 React 应用程序时候,我发现有一个简单模式非常有用。如果你已经用过 React 一段时间,可能你已经发现它了。有篇文章已经很好地解释了它,但是我还是想加点东西。

如果你将组件分为两类,你会发现组件更容易重用和推理。我称它们为容器(Container)组件和展示性(Presentational)组件,但是我也听说过 FatSkinnySmartDumbStatefulPureScreensComponents ,等等。这些叫法并非完全一样,但是核心思想却相似。

展示性组件:

  • 关心的是组件的外观。

  • 内部可能会既包含有展示性组件,也包含有容器组件,并且它自身通常会有一些 DOM 标记和样式。

  • 经常通过 this.props.children 来允许包含。

  • 对应用的其余部分(比如 Flux Action 或者 Store)没有依赖。

  • 不会指定数据如何加载或者变化。

  • 只通过 props 接收数据以及回调。

  • 极少有自己的状态(如果有的话,就是 UI 状态,而不是数据)。

  • 被写为函数式组件,除非它们需要状态、生命周期钩子(hook)、或者性能优化。

  • 示例: Page, Sidebar, Story, UserInfo, List。

容器组件:

  • 关心的是组件如何工作。

  • 内部可能会既包含有展示性组件,也包含有容器组件,但是它们自身通常没有任何 DOM 标记(除了一些包装用的 div),并且从没有任何样式。

  • 给展示性组件或者其它容器组件提供数据和行为。

  • 调用 Flux action,并将其提供给展示性组件作为回调。

  • 通常是有状态的,因为它们趋向于充当数据源。

  • 通常用更高阶的组件生成,比如 React Redux 的 connect() 、 Relay 的 createContainer() 、或者 Flux Utils 的 Container.create() ,而不是手写的。

  • 示例: UserPage, FollowersSidebar, StoryContainer, FollowedUserList.

我把它们放在不同的文件夹,以清晰区分。

这种方法的好处

  • 更好分离关注点。用这种方式写组件,可以更好理解你的应用程序和 UI。

  • 可重用性更好。你可以对完全不同的状态源使用相同的展示性组件,然后将其变成可以被进一步重用的不同容器组件。

  • 展示性组件本质上是你的应用程序的”调色板“。你可以把它们放在一页上,让设计师调整所有变化,而不用动应用程序的逻辑。你可以在该页面上执行截图回归测试(screenshot regression test)。

  • 这可以强制你提取“布局组件”,比如 Sidebar、Page、ContextMenu ,并使用 this.props.children ,而不是在不同的容器组件中复制相同的标记和布局。

记住:组件不必发射 DOM。 它们只需提供 UI 之间的组成边界问题。

利用这点。

什么时候引入容器?

我建议你先只用展示性组件创建你的应用。你会逐渐认识到你正向下给中间组件传递了太多 props。当你注意到一些组件并没有用到它们接受的 props,只是向下运送 props,并且一旦子组件需要更多数据,你不得不随时重写所有这些中间组件时,那就是引入一些容器组件的好时机。通过这种方式,你可以把数据和行为 props 传给分支组件,而不必烦扰组件树中间不相关的组件。

这是一个不断重构的过程,所以不要试图第一次就搞定。当你用熟了这种模式时,你会对什么时候抽取容器有一种直觉,就像你知道什么时候该抽取一个函数一样。我的免费 Redux Egghead 系列视频 也会对此有帮助!

其它分类

展示性组件和容器组件之间的区别并非是技术上的区别,而是用途上的区别,理解这一点很重要。

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

  • 有状态和无状态。有些组件用了 React 的 setState() 方法,有些没有用。容器组件趋向于是有状态的,展示性组件趋向于是无状态的,但是这不是一个硬性规定。展示性组件可以是有状态的,容器组件也可以是无状态的。

  • 类和函数从 React 0.14 开始 ,组件既可以声明为类,也可以声明为函数。函数式组件定义起来更简单,但是它们缺乏某些当前只有类组件可用的功能。这些限制中一些在未来可能会消失,但是现在是存在的。因为函数式组件更容易理解,我建议你一直用函数式组件,除非你需要状态、生命周期钩子或者性能优化,而这些功能目前只对类组件可用。

  • 纯和不纯。有人说如果一个组件被给了相同的 props 和状态,能返回相同的结果,那么该组件就是纯组件。纯组件既可以被定义为类,也可以被定义为函数,既可以是有状态,也可以是无状态的。纯组件的另一个重要特征是,它们不会依赖于 props 或者状态的深层变化,所以它们的渲染性能可以通过在它们的 shouldComponentUpdate() 钩子中做一个 shallow comparison 来优化。目前只有类可以定义 shouldComponentUpdate() ,但是以后可能会改变。

展示性组件和容器组件二者都可以归于这些分类的任何一个。以我的经验,展示性组件趋向于是无状态纯函数,容器趋向于是有状态纯类。但是这不是一个规则,只是一个观察,我已经看到在特定条件下完全相反的案例。

不要把展示性组件和容器组件分离作为一个教条。有时候它并不重要,或者很难划定界线。如果你对一个特定组件是否应该是展示性组件还是容器组件感到并不确定,那么可能是太早决定了。不要紧张!

示例

Michael Chan这个 gist 对此有一个很好的展示。

延伸阅读

附注

> * 在本文的早期版本中,我称之为 “smart” 和 “dumb” 组件,但是这对展示性组件过于严厉,更重要的是,这不能真正解释它们在用途上的区别。我更喜欢新的术语,我也希望你也喜欢!

> * 在这篇文章的早期版本中,我宣称展示性组件应该值包含其它展示性组件。我不再认为是这种情况。一个组件是否是展示性组件,还是容器组件,是其实现细节。你应该能用容器组件替换一个展示性组件,而不用修改任何调用位置。因此,展示性组件和容器组件可以包含其它展示性或者容器组件就好了。

相关文章