翻译 | 如何规模化 React 应用

朱乙(沪江前端开发工程师)
本文为原创翻译,如需转载请标明出处,不当之处敬请指出css

咱们最近发布了 React Boilerplate 3.0,在发布前几个月,咱们与数百位开发者进行了沟通,讨论了他们是如何构建和规模化WEB应用的。下面将咱们从中学到的东西分享给你们。前端

clipboard.png

很早咱们就意识到,咱们不但愿React Boilerplate仅仅是“另外一个模板项目”,咱们但愿它能为开发者提供创业或研发产品的最优解决方案。react

过去,规模化大多和服务器系统有关。随着使用的用户愈来愈多,你须要确保能在集群中添加更多的服务器,数据库可以拆分到多台服务器上。数据库

但如今,随着富WEB应用的崛起,规模化在前端也变得愈来愈重要!一个复杂应用的前端也须要能应付得了大量的用户、开发者和模块,须要重视这三个方面(用户、开发者和组件)的规模化,不然后面就会碰到问题。redux

容器和组件

对于一个大的应用,第一个大的改进就是,了解有状态(容器)和无状态(组件)组件的区别。容器管理数据和链接状态,一般没有样式。而组件则有样式,但不负责任何数据和状态的管理,最初我比较迷惑。基本上,容器负责让组件正常工做,组件负责东西如何展现。服务器

根据这一点,咱们清楚地区分了可复用组件和数据管理层。如今你去编辑组件时,就不用担忧会搞混数据结构;编辑容器,也不用担忧弄乱样式了。按照这个思路开发应用将会变得很简单!数据结构

代码结构

按照惯例,开发者们会按照类型来组织结构,好比使用 actions/,components/,containers/ 等文件夹。架构

假设有一个名称为NavBar的导航条容器,容器会管理与之相关的状态,还有一个toggleNav动做来打开关闭它。下面就是按类型组织的文件结构:app

react-app-by-type
├── css
├── actions
│ └── NavBarActions.js
├── containers
│ └── NavBar.jsx
├── constants
│ └── NavBarConstants.js
├── components
│ └── App.jsx
└── reducers
└── NavBarReducer.js

这种组织方式用来作样例还能够,但若是你有成百上千个组件,开发就会很困难。每添加一项功能,你就必须在上千个文件中找到正确的文件。这很是讨厌,也容易让人感到疲劳。异步

通过长期追踪咱们的 Github 问题列表和不少不一样尝试后,咱们找到了一种更好的解决方案:

那就是按照功能来组织代码!也就是,把和某个功能(好比导航条)相关的全部文件放到同一个文件夹里。

下面是从新组织的 NavBar 代码:

react-app-by-feature
├── css
├── containers
│    └── NavBar
│       
├── NavBar.jsx
│        ├── actions.js
│        ├── constants.js
│        └── reducer.js
└── components
└── App.jsx

开发这个应用,只须要进入一个目录,若是添加一个功能,也只须要建立一个目录。这种方式很是有利于文件的重命名、文件和替换,多人协做也不会致使任何冲突。

当我第一次知道这种开发React应用的方式时,我在想,我为何还要按照原来的思路作?这种开发方式太赞了!

须要注意的是,这种组织方式并不意味着 redux actions 和 reducers 只能用在这个组件里,它们能够(也应该)用于其余组件中!

当我按照这个思路开发的时候想到了两个问题:

1.该如何处理样式?
2.该如何处理数据获取?

样式管理

除告终构的考虑以外,因为CSS自己的两个特色,在基于组件的架构中使用 CSS 很难:全局命名和继承。

惟一的类名

假设在某个大型应用中存在这段 CSS代码:

.header { /* … */ }
.title {
       background-color: yellow;
}

很快你就会发现问题,title 太经常使用了。其余开发者也可能会写出如下代码:

.footer { /* … */ }
.title {
       border-color: blue;
}

这就产生了一个命名冲突,而后你的title样式就多了一个蓝色边框,让你不得不在上千个文件中找到这段代码。

幸运的是,有大牛为咱们找到了解决方案,那就是 CSS Modules。CSS Modules 的关键之处在于:把组件的样式放在组件本身的文件夹里。

react-app-with-css-modules
├── containers
└── components     
   └── Button         
      ├── Button.jsx         
      └── styles.css

如今的CSS看起来没有任何区别,不过咱们再也不担忧命名冲突了,可使用很是常见的名字:

.button {
   /* … */
}

而后就能够在组件中经过require或import引入这些CSS文件,并在 className 中使用了:

/* Button.jsx */
var styles = require('./styles.css');
<div className={styles.button}></div>

若是你如今去查看对应的 DOM 结构,你会看到<div class="MyApp__button__1co1k"></div>!CSS Modules 帮你保证了类名的“惟一”。

为每一个组件重置属性

在 CSS 中,特定的属性会在 DOM 节点上下继承。好比,若是父节点有 line-height,而子节点没有,子节点就会自动继承父节点的 line-height。

在基于组件的架构中,这可不是咱们想要的。好比下面的代码:

.header {
   line-height: 1.5em;
   /* … */
}

.footer {
   line-height: 1;
   /* … */
}

假设咱们在这两个组件中渲染一个 Button,这个 Button 在这两个组件里会有不一样的外观!这一点不只适用于 line-height,也适用于其余能继承的属性。

在之前,咱们能够用 Reset CSS、Normalize.css 和 sanitize.css 来重置样式表。但若是咱们想将这一理念落实到每个组件上呢?

这就是PostCSS 插件PostCSS Auto Reset 的用途!它会对每一个组件进行样式重置,将全部能继承的 CSS 属性设置成默认值,从而覆盖继承。

数据拉取

基于组件的架构面临的第二大问题就是数据拉取。对于大部分 action 来讲,将 action 和组件放在一块儿很合理,但数据拉取是全局 action,不和某个组件相连!

目前大多数开发者使用 Redux Thunk来处理Redux 中的数据拉取。标准的thunked action以下:

clipboard.png

这种在 action 中拉取数据的方法很棒,但存在两个问题:很难测试这些函数,以及在 action 中包含数据拉取看起来不太对。

Redux 的一大好处就是 pure action creator 很容易测试。而在 action 中加入了 thunk 的数据拉取操做以后,你必须两次调用 action,模拟 dispatch 函数等。

最近,redux-saga 在 React 世界引发了普遍关注。redux-saga 利用了 ES6 的 Generator 函数,让异步代码看起来就像同步代码同样,并且让异步流很是容易测试。redux-saga 给人的感受是一个单独处理全部异步事务的独立线程,不会干扰应用的其余方面!

下面是 redux-saga 的一个例子:

clipboard.png

上面的代码读起来就像小说同样,避免了回调地狱,并且很是容易测试。为何?这是由于 redux-saga 导出的这些“做用”(effect)无需完成便可进行测试。

咱们在文件顶部 import 的这些做用都是 handler,可让咱们轻松与 redux 交互:

  • put() 派发一个action

  • take() 暂停 saga 直到action发生

  • select() 获取部分 redux 状态(有点像 mapStateToProps)

  • call() 调用传入的第一个位置的函数,并将其余参数做为被调用函数的参数

为何这些做用有用?让咱们看看下面的测试:

clipboard.png

只有 generator.next() 被调用时,generator 函数才会继续,直到遇到下一个 yield 关键字!另外,咱们也把测试文件和组件放在了一块儿。

用 redux-saga 来作胶水中间件

咱们的组件如今真正解耦了。它们不关心其余组件的样式或逻辑;它们只关心本身的事情(绝大多数状况下如此)。

假设有一个 Clock 和一个 Timer 组件。当 Clock 中的按被按下时,咱们想要启动 Timer;当 Timer 中的中止按钮被按下时,咱们想要在 Clock 中显示时间。
你也许会这么作:

clipboard.png

但这么作的话,你没法单独使用这两个组件,复用它们几乎不可能!

不过,咱们能够用 redux-saga 来作这两个解耦组件的“胶水中间件”。取决于应用类型,咱们能够经过听取特定 action,来以不一样方式做出反应,从而让组件真正变得可复用。

先修改两个组件:

clipboard.png

注意两个组件如今只关心本身,只 import 了本身的 action !

下面用一个 saga 来把这两个解耦的组件链接到一块儿:

clipboard.png

漂亮!

总结

  • 容器和组件的区别

  • 按功能组织代码文件

  • 使用 CSS modules 和 PostCSS Auto Reset

  • 使用 redux-saga:

    • 得到可读而且可测试的异步流

    • 将解耦组件链接到一块儿

iKcamp原创新书《移动Web前端高效开发实战》已在亚马逊、京东、当当开售。

相关文章
相关标签/搜索