容器组件和展示组件(译)
本文翻译自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()
,未来可能会变更。
展示组件和容器组件都可以互相包含。在我的经验里,展示组件趋向于无状态的纯函数,容器组件往往是有状态的纯类。但这也只是一个观察不是一个规则,我也见过很多完全相反的例子,它们在特定情况下也很有意义。
不要让展示组件和容器组件作为一个区分的教条。有时会变得很困难去区分这条线。如果你不确信是否应该划分为展示组件或容器组件,往往太过早做决定了,不必紧张,时机会到的。