阅读该文章只需要8分钟
React 只是一个库,它并没有制定任何规则指导你该如何组织你的项目结构。这很好,因为它让我们可以自由尝试不同的组织方式,并选用更适合自己项目的方式。另一方面,这可能会给React开发初学者带来一些困惑。
我将在本文中展示一些我已经采用并扩展良好的方法。这些方法并没有重复造轮子,只是把圈内一些优秀实践组合在一起并加以改良。
记住: 这里提及的方法并不是一成不变的!你可以选用一些你觉得合理的方式
直接采用或者有所修改用于适应你自己的项目。
目录结构 (Folder Structure)
我经常碰到的一个问题是关于如何组织文件和文件夹的目录结构,在本文中,我先假定你使用 create-react-app
创建了一个最基本的目录结构,根目录下包含 .gitignore, package.json, README.md, yarn.lock
文件,以及public
和
src
文件夹,项目的源代码将存放于src
目录下。
目录结构如下图所示,本文将关注src
目录,其余的一切将保持原样。
容器和组件 (Containers and Components)
您可能已经在某个项目的根目录中尝试过分离容器组件(Containers)
和 演示组件(Presentation Components)
, 在src
中,创建名为components
和containers
的目录。
src
├─ components
└─ containers
然后,这样的方式会存在一些问题,如下所示:
主观规则 - 你并没有很清晰的规则区分什么是
容器组件
, 什么是展示组件
。
彼此之间的差异是很主观的,当你在一个团队里面,很难让所有的开发者都有相同的判断。没有考虑组件的动态性 - 即使当你确定某个组件是
容器组件
,也很容易在项目生命周期中变更成为展示组件
,并迫使你将它从容器组件
目录移动到展示组件
目录,反之亦然。允许组件重名 - 在一个应用中,
组件
应该具有声明性和唯一的命名以避免混淆每个组件
的职责。然而,上述的方法可产生漏洞让两个组件
重名,一个代表容器组件
,一个代表展示组件
。效率缺失 - 你需要不断的切换
containers
和components
目录,甚至你只是在开发同一个功能点,通常一个功能点会需要两种类型的组件。
这种方法还有一个变种,在模块内部保留这种分离。
想象一下在你的应用内部有一个 User
模块,你用两个目录分离你的组件
:
src
└─ User
├─ components
└─ containers
上述方法最大限度的降低了你在项目中切换目录的麻烦,同时也衍生了一些杂音。项目中的模块越多,将会产生越多的containers
和componenets
目录。
基于这些原因,当我们在讨论如何组织文件和目录时,用展示
和容器
的概念来区分组件
显得无关紧要,也就是说,我们可以把所有的组件
都存放在components
目录除了纯UI
的组件。
即便
展示组件
和容器
在区分文件夹部分是无关紧要,但了解他们之间的差异依然非常重要,如果对于这个概念你有任何疑问,建议你阅读该文Presentational and Container Components.
代码分离与分组
在components
目录下,我们按照模块/功能
方式分组文件。
对于 user
的 CRUD , 我们将会有一个 User
模块, 结构如下所示:
└─ components
└─ User
├─ Form.jsx
└─ List.jsx
当一个组件
由多个文件组成时,我们把组件
以及相关文件放在同名文件夹中。
例如,你的 Form.css
包含 Form.jsx
所需要的样式, 你的目录结构如下所示:
src
└─ components
└─ User
├─ Form
│ ├─ Form.jsx
│ └─ Form.css
└─ List.jsx
测试文件和代码文件放在同一个目录,上述例子,
Form.jsx
的测试文件可以命名为Form.spec.jsx
并存在相同目录。有没有一点眼熟的感觉,假如你了解google的前端框架
UI 组件
除了按模块
分离组件
之外,我们还在src/components
中包含一个UI
文件夹用于存放所以的公用组件
。
UI组件作为公用组件并不属于任何模块, 甚至是可以保存在开源的lib
,因为它们并不包含任何应用的任何业务逻辑。例如Buttons, Inputs, Checkboxes, Selects, Modals, Data display elements
等等。
组件命名
我们讨论过了按照模块
来组织和分离我们的组件,那么问题来了,如何为他们命名?
当我们讨论如何为
组件
命名,指的是给一个定义组件的类或者常量命名:
class MyComponent extends Component {
}
const MyComponent () => {};
为了方便查找和避免冲突,组件
的命名必须在应用中清晰且唯一。
当我们使用React Dev Tools
调试应用时,好的组件命名会变得非常方便,并在一些运行时错误(run-time errors)
中清晰得指出错误的组件。
我们遵循path-based-component-naming
原则,用基于src
或者components
目录的相对路径为组件命名,基本上一个组件的路径为 components/User/List.jsx
将会被命名为UserList
.
当文件名和所在目录重名时,我们不必再次重复,例如components/User/Form/Form.jsx
命名为UserForm
而不是UserFormForm
。
上述模式的一些好处如下:
搜索便利
假如你的编辑器支持模糊搜索,只需要搜索UserForm
就可以找到你需要的文件:
假如你通过文件夹目录查找文件,根据组件名称可以很容易的定位:
避免文件与文件夹名称重复
按照这种模式,我们根据上下文命名文件。上一段提及的Form
,我们知道它是一个User Form
,既然它已经在User
文件夹了,那么不必在文件名中重复出现User
,只需命名为Form.jsx
。
当我最初使用React
时,曾经使用全名(组件名)命名文件,这样导致同一名称多次重复出现(/screens/User/UserForm
)并且引用路径变得过于冗长。
请看一下两种方式的不同:
import ScreensUserForm from './screens/User/UserForm';
// vs
import ScreensUserForm from './screens/User/Form';
在上面的例子中,你也许看不到第二种方式有什么优势。但随着项目的不断增长,两种命名方式会有很大的不同,如下所示:
import MediaPlanViewChannel from '/MediaPlan/MediaPlanView/MediaPlanViewChannel.jsx';
// vs
import MediaPlanViewChannel from './MediaPlan/View/Channel';
那再想象一下在这个文件中引用5倍甚至更多的依赖,吧
基于这个原因,我们推荐根据文件的上下文来命名文件,component
则相对于components
或src
文件夹的位置来命名。
界面(Screens)
顾名思义,Screens就是我们在应用程序中界面。
在 user
的增删查改中,我们将会有 user
列表界面,新增user界面和修改user界面。
界面是你用组件为应用程序组合成的一个的页面,理想状况下,界面应该不包含任何的逻辑只是功能组件。
我们将界面(Screens)保存在src
根目录下单独的文件夹中,它们按路由(route)定义分组而不是模块:
src
├─ components
└─ screens
└─ User
├─ Form.jsx
└─ List.jsx
假设项目使用react-router
,我们把Root.jsx
保存在Screens
文件夹中,并在其中定义所有的应用程序路由。
Root. jsx
的代码类似于:
import React, { Component } from 'react';
import { Router } from 'react-router';
import { Redirect, Route, Switch } from 'react-router-dom';
import ScreensUserForm from './User/Form';
import ScreensUserList from './User/List';
const ScreensRoot = () => (
<Router>
<Switch>
<Route path="/user/list" component={ScreensUserList} />
<Route path="/user/create" component={ScreensUserForm} />
<Route path="/user/:id" component={ScreensUserForm} />
</Switch>
</Router>
);
export default ScreensRoot;
请注意,我们将所有screens
文件放在一个与路由同名的文件夹中,user/ ->User/
。为每个父路由(parent route)创建一个文件夹,并在其中定义子路由(sub-routes)。在上面例子中,我们创建了User
文件夹,并将ScreensUserList
和ScreensUserForm
保存在其中。在这种模式下,你通过url
轻松找到每个路由对应的渲染界面(screen)
。
一个界面(screen)可以同时为多个路由(route)渲染, 如上述例子中ScreensUserForm
同时为创建user和编辑user渲染。
你应该已经注意到所有的视图组件命名中都包含Screen
前缀。当组件文件存放于components
文件夹之外,我们应该用针对src
的相对路径进行命名。在src/screens/User/List.jsx
路径的组件应该命名为ScreensUserList
.
创建Root.jsx
后,我们的结构如下:
src
├─ components
└─ screens
├─ User
│ ├─ Form.jsx
│ └─ List.jsx
└─ Root.jsx
不要忘记在
index.js
中引用Root.jsx
作为应用程序的根组件(root component)
如果你对怎样定义screen
存在疑问,请看下面的user form
的screen
定义:
import React from 'react';
import UserForm from '../../components/User/Form/Form';
const ScreensUserForm = ({ match: { params } }) => (
<div>
<h1>
{`${!params.id ? 'Create' : 'Update'}`} User
</h1>
<UserForm id={params.id} />
</div>
);
export default ScreensUserForm;
最终,我们的应用程序结构如下所示:
src
├─ components
│ ├─ User
│ │ ├─ Form
│ │ │ ├─ Form.jsx
│ │ │ └─ Form.css
│ │ └─ List.jsx
│ └─ UI
│
└─ screens
├─ User
│ ├─ Form.jsx
│ └─ List.jsx
└─ Root.jsx
重述
- 展示组件(Presentational component)和容器组件(container component)都应该保存于
src/components
目录 - 按
模块/功能
分类组件,如User/List
- 将通用组件存于
src/components/UI
目录 - 保持
界面(screens)
简单,结构和代码最小 - 根据路由定义为
screens
分组,路由/user/list
对应的界面(screen)位于/src/screens/User/List.jsx
- 组件依据和
component
或src
相对路径来命名,鉴于此,位于src/components/User/List.jsx
应该命名为UserList
,位于src/screens/User/List
应该命名为ScreensUserList
- 组件文件名和所在文件夹重名时,组件名不需再重复,位于
src/components/User/List/List.jsx
的组件应该命名为UserList
而不是UserListList
最后
本文仅涵盖React项目结构的一些建议,Redux
部分将在后续的文章中介绍。
感谢你的阅读,如有你什么想法请留言,我会很乐意阅读和回复。
如果本文对你有所帮助,请点赞或者推荐给你的同事和朋友阅读,不胜感谢。
原文地址: https://hackernoon.com/structuring-projects-and-naming-components-in-react-1261b6e18d76
感谢 Vinicius Dacal