React : 展示组件 & 容器组件 附案例与视频
来源:互联网 发布:unity3d 联网授权 编辑:程序博客网 时间:2024/05/19 15:43
写React应用时,我发现了一种简单而有效的模式。如果你也写过一阵子React,或许你也已经发现它了。对于这种模式,这篇文章讲得不错,不过我还想再补充几点。
如果把组件 分为以下两类,对组件的复用和理解会更容易一些。我这两类组件称为 展示组件 和 容器组件。也有叫“胖的&瘦的”、“聪明的&笨的”、“包含状态的的&纯的”、“Screens and Components”的等等说法,这些说法并不完全一致,但核心理念大概相同。
展示组件的特性:
- 负责外观的展示
- 可能同时包含展示组件 & 容器组件,通常带有自身的 DOM标签 和 样式属性
- 通常可以通过
this.props.children
包含组件 - 不依赖应用中的其他组件,如FLUX的 actions 或 stores
- 对于如何加载、修改数据,不做具体规定
- 仅通过props来接收数据 和 回调函数
- 几乎没有自身的状态(就是有,也是UI状态,而不是数据)
- 除非组件需要状态、生命周期钩子(lifecycle hooks)、或者性能优化,否则一般写为函数式组件
- 例如 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的逻辑和功能都不会受到一丝影响。你可以在这个页面上进行screenshot regression测试。
- 迫使你从APP里“提炼”出“布局组件”,如Sidebar, Page, ContextMenu等。由此,你将不得不使用this.props.children,而非在若干容器组件内 重复使用一套布局相关的代码。
请注意,组件并不一定需要生成DOM。它们只需要提供UI之间的分界与组合关系。
好好利用这一点。
何时引入容器组件?
在刚开始写APP的时候,我建议你只写展示组件。先就这么写着,总会有一个时刻,你将注意到,有太多的属性需要传递给中间层的组件。有些组件根本用不上这些属性,传给它们的目的,仅仅是为了能继续向下传递属性。而且,当子组件需要更多的数据时,你不得不重写中间层的组件。当你意识到这些时,就是引入容器组件的好时机。通过使用容器组件,无需途径组件树中其他无关的组件,就可以直接将数据和方法属性传给末端的叶子组件中。
这种重构的过程是渐进的,别想着一步到位。随着你对这种模式日复一日地练习,对于何时使用容器组件,你会慢慢培养出一种直觉。这种感觉就像你知道啥时候应该抽象出函数一样。我在蛋头网(egghead)上的免费系列课程也于此会有所帮助。
其他的分类方法
需要注意的是,展示组件 和 容器组件 之间的区别,并非是技术上的,而是在用途上的。理解这一点很重要。
作为对比,这里列举一些相关(但不同)的技术上的区别
有状态和无状态。有的组件使用 React 的 setState() 方法,有的组件则不用。尽管容器组件多是有状态的,展示组件多是无状态的,但是这并非硬性规定。展示组件也可以是有状态的,容器组件也可以是无状态的。
类和函数。 从 React 0.14 开始 ,组件既可以声明为类,也可以声明为函数。虽然函数式组件更容易定义,但是它们缺乏某些当前只有类组件才有的功能。在未来,这些限制可能会渐渐取消,但是目前确实是存在的。因为函数式组件更容易理解,我建议你一般用函数式组件就好,除非你需要状态、生命周期钩子或性能优化等目前 类组件独有的功能。
纯和不纯。有人说,只要拿到相同的属性(props)和状态,就能返回相同的结果,那么该组件就是纯组件。纯组件既可以被定义为类,也可以被定义为函数,既可以是有状态,也可以是无状态的。纯组件的另一个重要特征是,它们不会依赖于属性(props) 或者状态的深层变化(deep mutations),所以它们的渲染性能可以在 shouldComponentUpdate() 钩子中通过 shallow comparison 来优化。目前只有类可以定义
shouldComponentUpdate()
,以后可能会放宽限制。
不论是展示组件,还是容器组件,都可能是上面所列举的任意一种。以我的经验看,展示组件多是无状态的纯函数,而容器组件多是有状态的纯类。不过,这并非规定,而是经验之谈。我确实见过完全相反,但在特定条件下成立的例子。
别把展示组件/容器组件的这种分类方法视作教条。有时候其实无所谓,有时候又难以分辨。如果你对某个组件属于展示组件还是容器组件举棋不定,别急,或许还没到下结论的时候。
例子
Michael Chan的这一篇真的说到点子上了。
延伸阅读
- Getting Started with Redux
- Mixins are Dead, Long Live Composition
- Container Components
- Atomic Web Design
- Building the Facebook News Feed with Relay
译者注
注1
一个具体的组件拆分案例
在作者推荐的Michael Chan的这一篇文章里,有一个具体的例子,对理解本文颇有脾益,摘录于下:
A component like this would be rejected in code review for having both a presentation and data concern:
// CommentList.jsclass CommentList extends React.Component { constructor(props) { super(props); this.state = { comments: [] } } componentDidMount() { $.ajax({ url: "/my-comments.json", dataType: 'json', success: function(comments) { this.setState({comments: comments}); }.bind(this) }); } render() { return <ul> {this.state.comments.map(renderComment)} </ul>; } renderComment({body, author}) { return <li>{body}—{author}</li>; }}
It would then be split into two components. The first is like a traditional template, concerned only with presentation, and the second is tasked with fetching data and rendering the related view component.
// CommentList.jsclass CommentList extends React.Component { constructor(props) { super(props); } render() { return <ul> {this.props.comments.map(renderComment)} </ul>; } renderComment({body, author}) { return <li>{body}—{author}</li>; }}
// CommentListContainer.jsclass CommentListContainer extends React.Component { constructor() { super(); this.state = { comments: [] } } componentDidMount() { $.ajax({ url: "/my-comments.json", dataType: 'json', success: function(comments) { this.setState({comments: comments}); }.bind(this) }); } render() { return <CommentList comments={this.state.comments} />; }}
In the updated example, CommentListContainer could shed JSX pretty simply.
render() { return React.createElement(CommentList, { comments: this.state.comments });}
注2
Dan结合案例讲解这两种组件的视频课
Redux: Extracting Presentational Components (Todo, TodoList)
Redux: Extracting Presentational Components (AddTodo, Footer, FilterLink)
Redux: Extracting Container Components (FilterLink)
Redux: Extracting Container Components (VisibleTodoList, AddTodo)
注3
一个对展示组件进行简化以及传参的例子
原文链接
Presentational and Container Components
- React : 展示组件 & 容器组件 附案例与视频
- 容器组件和展示组件react-redux
- 组件与容器
- JavaWeb 容器与组件
- 译文《容器组件和展示组件》原作者:Dan Abramov
- React 自定义组件与组件复用
- React受控组件与非受控组件
- React 之 组件与组件抽离
- React受控组件与非受控组件
- React 组件
- React组件
- react 组件
- React组件
- React 组件
- react 组件
- react组件
- react 组件
- React组件
- 起航篇
- 分数矩阵
- codeforces 887 C. Solution for Cube(Codeforces Round #444 (Div. 2))
- Python-Celery的使用
- springmvc @RequestParam 获取参数 HTTP Status 400
- React : 展示组件 & 容器组件 附案例与视频
- GitHub退出登录账号操作步骤
- tcp传输的三次握手和四次挥手简单理解
- 流程图解Spring Framework(一) spring 如何创建一个Bean的
- Poll与Epoll 区别总结
- Java equals == 简单分析
- 【bzoj2393】Cirno的完美算数教室
- 蓝桥杯 算法提高 5-3日历
- Postman学习部分资料整理