[译]如何更好的组织 React 应用

原文 Url: https://medium.com/@alexmngn/how-to-better-organize-your-react-applications-2fd3ea1920f1


最近几年我和十余个同伴一起致力于大型项目从零开始的开发和维护. 有时候,没有一个良好的架构很难维持代码组织

注1: 我的例子中都使用了 redux, 如果你不了解, 参考 redux文档

注2: 所有的例子都基于 react, 但在 react native 中可以保持相同的结构

创建一个应用有哪些挑战?

这是一个发生或将发生在所有开发者项目中的事情:

  1. 你和几个开发者开始为客户开发一个应用, 一切都很美好

  2. 你的客户提了几个新需求, 嗯, 加上了

  3. 你的客户又要求去掉几个功能, 并加上一些新功能, 有点棘手但并不难实现. 项目开始变得不那么完美

  4. 你的客户继续要求增加/修改/删除需求, 你开始在一些代码上打补丁. 代码不再让人骄傲, 但还能用

  5. 6个月后, 代码变得复杂, 难以理解, 像一团意大利面条

直到有一天客户要求推出一个新版本时, 你放弃了乱成一团的旧代码, 开始了一个新的项目


自从我开始学习 react 时, 我读了许多文章关于如何创建一个 todo list或者之类的小玩意. 这些文章对理解 react 很有帮助. 但很快的我发现自己很难找到一些文章关于如何开发好一个有十几个页面, 几百个组件的应用.

一段时间搜索后, 我发现 react 的样板项目差不多都有相同的结构, 所有文件按类型划分, 例如这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/src
/actions
/notifications.js

/components
/Header
/Footer
/Notifications
/index.js
/containers
/Home
/Login
/Notifications
/index.js
/images
/logo.png
/reducers
/login.js
/notifications.js
/styles
/app.scss
/header.scss
/home.scss
/footer.scss
/notifications.scss
/utils
index.js

这种架构或许可行, 但我认为并不是最好的.

这种按类型划分结构的代码规模增长后, 往往会变得难以维护. 而认识这一点时往往已经太迟了. 你需要花费大量的时间和金钱才能改善, 或者在接下来几年将就着维护.

然而好的一方面, 你可以按照任何一种你喜欢的方法去组织 react 项目, react 只是一个js类库.

什么样的组织方法更好呢

许多年前我在金融公司用 Ember 做应用. 这种 Ember 应用有趣的一点是, 项目目录是按照功能划分的, 而非类型. 这改变了一切.

Pods 在 Ember 中很强大, 但仍有限制, 我想要更加灵活的东西. 随着经验积累, 我试着使用一种更好的结构, 按照功能划分结构, 并按需嵌套. 现在的结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/src
/components
/Button
/Notifications
/components
/ButtonDismiss
/images
/locales
/specs
/index.js
/styles.scss
/index.js
/styles.scss
/data
/users
/actions.js
/api.js
/reducer.js
/scenes
/Home
/components
/ButtonLike
/services
/processData
/index.js
/styles.scss
/Sign
/components
/FormField
/scenes
/Login
/Register
/locales
/specs
/index.js
/styles.scss
/services
/api
/geolocation
/session
/actions.js
/index.js
/reducer.js
index.js
store.js

所有的组件, 场景, 服务(一个需求)都可以独立运作, 包含了自身需要的图片, 样式, 翻译, action 和单元测试. 每一个功能都可以单独被项目应用(有一点像 node 的 modules).

为了保证可以运作, 必须遵守如下规则:

  1. 组件可以定义嵌套组件或服务, 但不能使用或定义场景

  2. 场景可以定义嵌套组件, 服务和场景

  3. 服务可以定义嵌套服务, 但不能定义组件和场景

  4. 数据需求是独立的

  5. 能允许父子关系的嵌套 (不允许跨目录使用)

组件(Component)

你已经知道组件是什么, 但这种组织下重要的一点是, 组件可以嵌套组件

组件定义在项目根目录下. 组件目录里的东西是全局的可以在项目任何地方使用. 但如果你决定在组件目录下创建一个新的组件目录(嵌套). 这个新的组件目录只能由其直属父目录调用

为什么要这样呢

当开发一个大型应用时, 经常会遇到你需要创建一个肯定不会复用的组件. 如果这个组件放在组件的根目录上, 它将迷失在上百个组件之中. 你可以将之分类, 但等到清理的时候你可能已经不记得哪些组件在哪里用到了.

但如果组件的根目录上只有主要的组件, 比如各种按钮, 表单项, 缩略图, 以及一些复杂的拥有子组件的, 像是评论列表, 混合表单等, 读起来就会轻松许多.

例如:

1
2
3
4
5
6
7
8
9
10
11
/src
/components
/Button
/index.js
/Notifications
/components
/ButtonDismiss
/index.js
/actions.js
/index.js
/reducer.js
  1. Buttons 可以在任何地方被调用

  2. Notifications 也可以在任何地方被调用, 它定义了子组件 ButtonDismiss. 你不能在 Notifications之外的地方使用 ButtonDismiss

  3. ButtonDismiss 可以使用 Button

场景(Scenes)

场景是应用中的一个页面. 你可以像组件一样使用场景. 但我更喜欢将之隔离开.

如果你使用了 react-router, 你可以将所有场景在 index.js 中引入并设置路由表.

和组件一样, 场景也可以嵌套场景, 并且可以在场景中定义组件和服务. 必须记得一点: 在场景中定义的组件和服务只能在这个场景中使用

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/src
/scenes
/Home
/components
/ButtonShare
/index.js
/index.js
/Sign
/components
/ButtonHelp
/index.js
/scenes
/Login
/components
/Form
/index.js
/ButtonFacebookLogin
/index.js
/index.js

/Register
/index.js
/index.js
  1. Home 中定义了组件 ButtonShare, 它只能在 Home 场景下使用

  2. Sign 定义了组件 ButtonHelp, 它可以在 LoginRegister 场景及其所属的组件中使用

  3. Form 组件可以使用 ButtonHelp 因为 ButtonHelp 在其父目录下.

  4. Register 不能用 Login 下的组件, 但可以用 ButtonHelp

服务(Service)

不是所有东西都能组件化, 你需要创建一个独立模块来支持组件和场景.

你可以让一个自用的服务(self-contained module), 或应用内任何地方重用. 这取决于具体项目的复杂度.

1
2
3
4
5
6
7
8
9
10
11
12
/src
/services
/api
/services
/handleError
/index.js
/index.js
/geolocation
/session
/actions.js
/index.js
/reducer.js

数据(Data)

data 实体和 service 很相似. 这是服务端 API与客户端之间的桥梁(bridge) 或 适配器(adapter). 当然没有 server Api 的话就不需要 data.

data 负责大部分的网络通讯, 读写内容, 传输数据或存储数据(比如 redux). 通常 data 能被任何组件或场景调用.

后记

如果你有兴趣看看具体项目, 作者 GitHub 上有两个参考

  1. https://github.com/alexmngn/react-rock-paper-scissors (ReactJS)

  2. https://github.com/alexmngn/react-native-authentication (React-Native)