如何组织一个大型的React应用并使其规模化

125 阅读7分钟

An astronaut constructing a space colony in the shape of the React logo

这篇文章的作者是特邀作者杰克-富兰克林。SitePoint的客座文章旨在为你带来网络社区的知名作家和演讲者的精彩内容。

在这篇文章中,我将讨论我在构建和构造大型React应用程序时采取的方法。React最好的特点之一是它如何摆脱你的方式,当涉及到文件结构时,它是任何东西,但描述性的。因此,你会在Stack Overflow和类似的网站上发现很多问题,询问如何构造应用程序。这是一个非常有意见的话题,没有一个正确的方法。在这篇文章中,我将告诉你我在构建React应用程序时做出的决定:选择工具、结构化文件以及将组件分解成小块。

An astronaut constructing a space colony in the shape of the React logo

构建工具和提示

对你们中的一些人来说,我是构建我的项目的webpack的超级粉丝,这并不奇怪。虽然它是一个复杂的工具,但团队在第五版中所做的大量工作和新的文档网站使它变得更加容易。一旦你进入webpack,并在你的头脑中形成概念,你真的有难以置信的力量可以利用。我使用Babel来编译我的代码,包括React特定的转换,如JJS,和webpack-dev-server来服务我的网站。我个人发现热重载并没有给我带来那么多好处,所以我对webpack-dev-server及其自动刷新页面的功能非常满意。

我使用ES模块,首次在ES2015中引入(通过Babel转译)来导入和导出依赖关系。这种语法已经存在了一段时间,虽然webpack可以支持CommonJS(又称Node风格的导入),但对我来说,开始使用最新、最先进的语法是有意义的。此外,webpack可以从使用ES2015模块的捆绑包中移除死代码,虽然不是很完美,但这是一个非常方便的功能,而且随着社区向ES2015的npm发布代码,这个功能会变得更加有利。大多数网络生态系统已经转向ES模块,所以这是我开始的每个新项目的明显选择。这也是大多数工具所期望支持的,包括其他捆绑器,如Rollup,如果你不想使用webpack。

文件夹结构

没有一个正确的文件夹结构适用于所有React应用程序。(就像本文的其他部分一样,你应该根据你的喜好来改变它。) 但以下是对我来说很有效的方法。

代码住在src

为了使事情井井有条,我把所有的应用程序代码放在一个叫src 的文件夹里。这里只包含最终捆绑的代码,没有其他内容。这很有用,因为你可以告诉Babel(或任何其他作用于你的应用程序代码的工具)只看一个目录,确保它不处理任何不需要的代码。其他代码,如webpack配置文件,则住在一个适当命名的文件夹中。例如,我的顶层文件夹结构经常包含。

- src => app code here
- webpack => webpack configs
- scripts => any build scripts
- tests => any test specific code (API mocks, etc.)

通常情况下,在顶层的文件只有index.htmlpackage.json ,以及任何dotfiles,如.babelrc 。有些人喜欢把Babel配置放在package.json ,但我发现这些文件在有很多依赖关系的大项目中会变得很大,所以我喜欢用.eslintrc.babelrc ,等等。

React组件

一旦你有了一个src 文件夹,棘手的问题是决定如何组织你的组件。过去,我把所有的组件放在一个大的文件夹里,比如src/components ,但我发现在大的项目中,这样做很快就会被淹没了。

一个常见的趋势是为 "聪明 "和 "愚蠢 "的组件(也被称为 "容器 "和 "展示 "组件)建立文件夹,但我个人从未发现明确的文件夹对我有用。虽然我确实有一些组件可以松散地分为 "聪明 "和 "愚蠢"(我将在下面详细讨论),但我并没有为每一个组件设置特定的文件夹。

我们根据应用程序中使用的区域对组件进行分组,同时为整个应用程序中使用的通用组件(按钮、页眉、页脚--通用且非常可重用的组件)建立一个core 文件夹。其余的文件夹映射到应用程序的一个特定区域。例如,我们有一个名为cart 的文件夹,包含所有与购物车视图有关的组件,还有一个名为listings 的文件夹,包含在页面上列出用户可以购买的东西的代码。

对文件夹进行分类也意味着你可以避免在组件前面加上它们所使用的应用程序的区域。举个例子,如果我们有一个显示用户购物车总成本的组件,与其叫它CartTotal ,不如用Total ,因为我是从cart 文件夹中导入的。

import Total from '../cart/total'
// vs
import CartTotal from '../cart/cart-total'

这是我发现自己有时会打破的规则。额外的前缀可以说明问题,特别是当你有两到三个类似名称的组件时,但通常这种技术可以避免额外的重复名称。

倾向于使用jsx 扩展名而不是大写字母

很多人在命名React组件时,都会在文件中使用大写字母,以区别于普通的JavaScript文件。所以在上面的导入中,文件会是CartTotal.js ,或者Total.js 。我倾向于坚持使用小写的文件,用破折号作为分隔符,所以为了区分,我为React组件使用了.jsx 扩展名。因此,我坚持使用cart-total.jsx

这有一个小的额外好处,那就是通过将你的搜索限制在带有.jsx 的文件上,你甚至可以在这些文件上应用特定的webpack插件,从而能够轻松地搜索你的React文件。

无论你选择哪种命名规则,重要的是你要坚持它。在你的代码库中组合各种约定,随着代码库的增长,你必须对其进行导航,这将很快成为一场噩梦。你可以使用eslint-plugin-react的规则来执行这个.jsx 惯例。

每个文件只有一个React组件

根据前面的规则,我们坚持一个React组件文件的惯例,并且该组件应该总是默认导出。

通常,我们的React文件是这样的。

import React from 'react'

export default function Total(props) {
  …
}

在我们必须包装组件以便将其连接到Redux数据存储的情况下,例如,完全包装的组件成为默认导出。

import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'

export default function Total(props) {
  …
}

export default connect(() => {…})(Total)

你会注意到,我们仍然导出原始组件。这对测试非常有用,你可以使用 "普通 "组件,而不必在单元测试中设置Redux。

通过保持组件的默认导出,可以很容易地导入组件并知道如何获取它,而不是必须查找确切的名称。这种方法的一个缺点是,导入者可以随意调用该组件。再一次,我们对此有一个惯例:导入应该以文件的名字命名。因此,如果你要导入total.jsx ,这个组件应该被导入为Totaluser-header.jsx 变成UserHeader ,以此类推。

值得注意的是,每个文件一个组件的规则并不总是被遵守。如果你最终建立了一个小的组件来帮助你渲染部分数据,而它只在一个地方使用,那么把它和使用它的组件放在同一个文件中往往更容易。把组件放在不同的文件中是有代价的:有更多的文件,更多的导入,一般来说,作为一个开发者要遵循更多的东西,所以要考虑这是否值得。像本文中的大多数建议一样,它们是有例外的规则。

继续阅读:如何组织一个大型的React应用程序,并使其SitePoint扩展