大家已经知道,只会接受 props 并且渲染确定结果的组件我们把它叫做 Dumb 组件,这种组件只关心一件事情 —— 根据 props 进行渲染。

Dumb 组件最好不要依赖除了 React.js 和 Dumb 组件以外的内容。它们不要依赖 Redux 不要依赖 React-redux。这样的组件的可复用性是最好的,其他人可以安心地使用而不用怕会引入什么奇奇怪怪的东西。

当我们拿到一个需求开始划分组件的时候,要认真考虑每个被划分成组件的单元到底会不会被复用。如果这个组件可能会在多处被使用到,那么我们就把它做成 Dumb 组件。

我们可能拆分了一堆 Dumb 组件出来。但是单纯靠 Dumb 是没有办法构建应用程序的,因为它们实在太“笨”了,对数据的力量一无所知。所以还有一种组件,它们非常聪明(smart),城府很深精通算计,我们叫它们 Smart 组件。它们专门做数据相关的应用逻辑,和各种数据打交道、和 Ajax 打交道,然后把数据通过 props 传递给 Dumb,它们带领着 Dumb 组件完成了复杂的应用程序逻辑。

Smart 组件不用考虑太多复用性问题,它们就是用来执行特定应用逻辑的。Smart 组件可能组合了 Smart 组件和 Dumb 组件;但是 Dumb 组件尽量不要依赖 Smart 组件。因为 Dumb 组件目的之一是为了复用,一旦它引用了 Smart 组件就相当于带入了一堆应用逻辑,导致它无法无用,所以尽量不要干这种事情。一旦一个可复用的 Dumb 组件之下引用了一个 Smart 组件,就相当于污染了这个 Dumb 组件树。如果一个组件是 Dumb 的,那么它的子组件们都应该是 Dumb 的才对。

划分 Smart 和 Dumb 组件

知道了组件有这两种分类以后,我们来重新审视一下之前的 make-react-redux 工程里面的组件,例如 src/Header.js

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux' class Header extends Component {
static propTypes = {
themeColor: PropTypes.string
} render () {
return (
<h1 style={{ color: this.props.themeColor }}>React.js 小书</h1>
)
}
} const mapStateToProps = (state) => {
return {
themeColor: state.themeColor
}
}
Header = connect(mapStateToProps)(Header) export default Header

这个组件到底是 Smart 还是 Dumb 组件?这个文件其实依赖了 react-redux,别人使用的时候其实会带上这个依赖,所以这个组件不能叫 Dumb 组件。但是你观察一下,这个组件在 connect 之前它却是 Dumb 的,就是因为 connect 了导致它和 context 扯上了关系,导致它变 Smart 了,也使得这个组件没有了很好的复用性。

为了解决这个问题,我们把 Smart 和 Dumb 组件分开到两个不同的目录,不再在 Dumb 组件内部进行 connect,在 src/ 目录下新建两个文件夹 components/ 和 containers/

src/
components/
containers/

我们规定:所有的 Dumb 组件都放在 components/ 目录下,所有的 Smart 的组件都放在 containers/ 目录下,这是一种约定俗成的规则。

删除 src/Header.js,新增 src/components/Header.js

import React, { Component } from 'react'
import PropTypes from 'prop-types' export default class Header extends Component {
static propTypes = {
themeColor: PropTypes.string
} render () {
return (
<h1 style={{ color: this.props.themeColor }}>React.js 小书</h1>
)
}
}

现在 src/components/Header.js 毫无疑问是一个 Dumb 组件,它除了依赖 React.js 什么都不依赖。我们新建 src/container/Header.js,这是一个与之对应的 Smart 组件:

import { connect } from 'react-redux'
import Header from '../components/Header' const mapStateToProps = (state) => {
return {
themeColor: state.themeColor
}
}
export default connect(mapStateToProps)(Header)

它会从导入 Dumb 的 Header.js 组件,进行 connect 一番变成 Smart 组件,然后把它导出模块。

这样我们就把 Dumb 组件抽离出来了,现在 src/components/Header.js 可复用性非常强,别的同事可以随意用它。而 src/containers/Header.js 则是跟业务相关的,我们只用在特定的应用场景下。我们可以继续用这种方式来重构其他组件。

组件划分原则

接下来的情况就有点意思了,可以趁机给大家讲解一下组件划分的一些原则。我们看看这个应用原来的组件树:

对于 Content 这个组件,可以看到它是依赖 ThemeSwitch 组件的,这就需要好好思考一下了。我们分两种情况来讨论:Content 不复用和可复用。

Content 不复用

如果产品场景并没有要求说 Content 需要复用,它只是在特定业务需要而已。那么没有必要把 Content 做成 Dumb 组件了,就让它成为一个 Smart 组件。因为 Smart 组件是可以使用 Smart 组件的,所以 Content 可以使用 Dumb 的 ThemeSwitch 组件 connect 的结果。

新建一个 src/components/ThemeSwitch.js

import React, { Component } from 'react'
import PropTypes from 'prop-types' export default class ThemeSwitch extends Component {
static propTypes = {
themeColor: PropTypes.string,
onSwitchColor: PropTypes.func
} handleSwitchColor (color) {
if (this.props.onSwitchColor) {
this.props.onSwitchColor(color)
}
} render () {
return (
<div>
<button
style={{ color: this.props.themeColor }}
onClick={this.handleSwitchColor.bind(this, 'red')}>Red</button>
<button
style={{ color: this.props.themeColor }}
onClick={this.handleSwitchColor.bind(this, 'blue')}>Blue</button>
</div>
)
}
}

这是一个 Dumb 的 ThemeSwitch。新建一个 src/containers/ThemeSwitch.js

import { connect } from 'react-redux'
import ThemeSwitch from '../components/ThemeSwitch' const mapStateToProps = (state) => {
return {
themeColor: state.themeColor
}
}
const mapDispatchToProps = (dispatch) => {
return {
onSwitchColor: (color) => {
dispatch({ type: 'CHANGE_COLOR', themeColor: color })
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ThemeSwitch)

这是一个 Smart 的 ThemeSwitch。然后用一个 Smart 的 Content 去使用它,新建 src/containers/Content.js

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import ThemeSwitch from './ThemeSwitch'
import { connect } from 'react-redux' class Content extends Component {
static propTypes = {
themeColor: PropTypes.string
} render () {
return (
<div>
<p style={{ color: this.props.themeColor }}>React.js 小书内容</p>
<ThemeSwitch />
</div>
)
}
} const mapStateToProps = (state) => {
return {
themeColor: state.themeColor
}
}
export default connect(mapStateToProps)(Content)

删除 src/ThemeSwitch.js 和 src/Content.js,在 src/index.js 中直接使用 Smart 组件:

...
import Header from './containers/Header'
import Content from './containers/Content'
...

这样就把这种业务场景下的 Smart 和 Dumb 组件分离开来了:

src
├── components
│ ├── Header.js
│ └── ThemeSwitch.js
├── containers
│ ├── Content.js
│ ├── Header.js
│ └── ThemeSwitch.js
└── index.js

Content 可复用

如果产品场景要求 Content 可能会被复用,那么 Content 就要是 Dumb 的。那么 Content 的之下的子组件 ThemeSwitch 就一定要是 Dumb,否则 Content 就没法复用了。这就意味着 ThemeSwitch 不能 connect,即使你 connect 了,Content 也不能使用你 connect 的结果,因为 connect 的结果是个 Smart 组件。

这时候 ThemeSwitch 的数据、onSwitchColor 函数只能通过它的父组件传进来,而不是通过 connect 获得。所以只能让 Content 组件去 connect,然后让它把数据、函数传给 ThemeSwitch

这种场景下的改造留给大家做练习,最后的结果应该是:

src
├── components
│ ├── Header.js
│ ├── Content.js
│ └── ThemeSwitch.js
├── containers
│ ├── Header.js
│ └── Content.js
└── index.js

可以看到对复用性的需求不同,会导致我们划分组件的方式不同。

总结

根据是否需要高度的复用性,把组件划分为 Dumb 和 Smart 组件,约定俗成地把它们分别放到 components 和 containers 目录下。

Dumb 基本只做一件事情 —— 根据 props 进行渲染。而 Smart 则是负责应用的逻辑、数据,把所有相关的 Dumb(Smart)组件组合起来,通过 props 控制它们。

Smart 组件可以使用 Smart、Dumb 组件;而 Dumb 组件最好只使用 Dumb 组件,否则它的复用性就会丧失。

要根据应用场景不同划分组件,如果一个组件并不需要太强的复用性,直接让它成为 Smart 即可;否则就让它成为 Dumb 组件。

还有一点要注意,Smart 组件并不意味着完全不能复用,Smart 组件的复用性是依赖场景的,在特定的应用场景下是当然是可以复用 Smart 的。而 Dumb 则是可以跨应用场景复用,Smart 和 Dumb 都可以复用,只是程度、场景不一样。

下一节:实战分析:评论功能(七)

上一节:使用真正的 Redux 和 React-redux

Smart 组件 vs Dumb 组件的更多相关文章

  1. 谈谈我对前端组件化中“组件”的理解,顺带写个Vue与React的demo

    前言 前端已经过了单兵作战的时代了,现在一个稍微复杂一点的项目都需要几个人协同开发,一个战略级别的APP的话分工会更细,比如携程: 携程app = 机票频道 + 酒店频道 + 旅游频道 + ..... ...

  2. BenUtils组件和DbUtils组件

    BenUtils组件和DbUtils组件 [TOC] 1.BenUtils组件 1.1.简介 程序中对javabean的操作很频繁,所有Apache提供了一套开源api,方便javabean的操作!即 ...

  3. JS组件系列——Bootstrap组件福利篇:几款好用的组件推荐(二)

    前言:上篇 JS组件系列——Bootstrap组件福利篇:几款好用的组件推荐 分享了几个项目中比较常用的组件,引起了许多园友的关注.这篇还是继续,因为博主觉得还有几个非常简单.实用的组件,实在不愿自己 ...

  4. JS组件系列——表格组件神器:bootstrap table

    前言:之前一直在忙着各种什么效果,殊不知最基础的Bootstrap Table用法都没有涉及,罪过,罪过.今天补起来吧.上午博主由零开始自己从头到尾使用了一遍Bootstrap Table ,遇到不少 ...

  5. JS组件系列——表格组件神器:bootstrap table(二:父子表和行列调序)

    前言:上篇 JS组件系列——表格组件神器:bootstrap table 简单介绍了下Bootstrap Table的基础用法,没想到讨论还挺热烈的.有园友在评论中提到了父子表的用法,今天就结合Boo ...

  6. JS组件系列——表格组件神器:bootstrap table(三:终结篇,最后的干货福利)

    前言:前面介绍了两篇关于bootstrap table的基础用法,这章我们继续来看看它比较常用的一些功能,来个终结篇吧,毛爷爷告诉我们做事要有始有终~~bootstrap table这东西要想所有功能 ...

  7. WPF中实例化Com组件,调用组件的方法时报System.Windows.Forms.AxHost+InvalidActiveXStateException的异常

    WPF中实例化Com组件,调用组件的方法时报System.Windows.Forms.AxHost+InvalidActiveXStateException的异常 在wpf中封装Com组件时,调用组件 ...

  8. react native 之子组件和父组件之间的通信

    react native开发中,为了封装性经常需要自定义组件,这样就会出现父组件和子组件,那么怎么在父组件和子组件之间相互通信呢,也就是怎么在各自界面push和pop.传值. 父组件传递给子组件: 父 ...

  9. vuejs动态组件给子组件传递数据

    vuejs动态组件给子组件传递数据 通过子组件定义时候的props可以支持父组件给子组件传递数据,这些定义的props在子组件的标签中使用绑定属性即可,但是如果使用的是<component> ...

随机推荐

  1. vue 数组和对象渲染问题

    vue 数组和对象渲染问题 最近项目有点忙碌,遇到好多问题都没有总结(╥﹏╥),在开发过程中,取vuex中的数组渲染完成之后,再次修改数组的值,数据更新了,但是视图并没有更新.以为是数组更新的问题,后 ...

  2. 修改DEDE系统数据库表前缀

    1,修改之前我们先备份下数据(哥们儿之前没有备份,我艹,害苦了),备份的操作过程是:网站后台------系统------数据库备份/还原-------然后按提交.默认保存的数据在data/backup ...

  3. 织梦系统如何设置URL绝对路径及绝对路径的好处

    今天,和大家分享下织梦系统如何设置URL绝对路径及绝对路径的好处,我的一些就是用的织梦系统,感觉织梦在SEO优化方面做的还是非常好的,至少在CMS系统中应该是做的最出色的吧!下面,我就先来讲下这个织梦 ...

  4. 转:创建表空间,删除后再次创建,报错ORA-01119

    原文:http://www.it2down.com/it-oracle-develop/57816.htm 我是个ORACLE新手,在删除了表空间,然后再创建怎么会提示出错? 删除表空间:drop t ...

  5. DP专辑之线性DP

    POJ1390 题目链接:http://poj.org/problem?id=1390 分类:记忆化搜索 dp[i][j][k] 表示,从i到j块且j后面有k块与第j块的颜色一样.dp[l][r][k ...

  6. 【USACO 2006 November Gold】Corn Fields

    [题目链接] 点击打开链接 [算法] 状压DP [代码] #include<bits/stdc++.h> using namespace std; #define MAXN 12 #def ...

  7. 任务49:Identity MVC:Model前端验证

    任务49:Identity MVC:Model前端验证 前端验证使用的是jquery的validate的组件 _ValidationScriptsPartial.cshtml 在我们的layout里面 ...

  8. CodeForces 712C Memory and De-Evolution (贪心+暴力)

    题意:现在有一个长度为 x 的正三角形,每次可以把一条边减小,然后用最少的时间变成长度为 y 的正三角形. 析:一开始,正着想,然后有一个问题,就是第一次减小多少才能最快呢?这个好像并不好确定,然后我 ...

  9. 斯坦福CS231n—深度学习与计算机视觉----学习笔记 课时2

    课时2 计算机视觉历史回顾与介绍中 1966年是计算机视觉的诞生年. 视觉处理流程的第一步,是对简单的形状结构处理,边缘排列. 边缘决定了结构. David Marr写了一本非常有影响力的书,视觉是分 ...

  10. 洛谷 - P1403 - 约数研究 - 数论

    https://www.luogu.org/problemnew/show/P1403 可以直接用线性筛约数个数求出来,但实际上n以内i的倍数的个数为n/i的下整,要求的其实是 $$\sum\limi ...