React 做为一个库,它没有规定项目的总体结构。这很好,由于它给了咱们自由去尝试不一样的方法,并适应更适合咱们的方式。另外一方面,这可能会给React领域的开发人员带来一些困惑。css
我将会在本文为你们展现我已经使用过一段时间而且效果不错的方式,这些方式没有经过从新造轮子来实现,而是经过将社区中的方案组合和提炼获得。前端
想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你!react
我常常遇到的一个问题是如何组织文件和目录结构。在这篇文章中,咱们认为你已有一个最小的结构,就像用 create-react-app
建立的结构同样。git
create-react-app
为咱们生成了一个基础的项目,包含根目录还有诸如.gitignore
, package.json
, README.md
, yarn.lock
的文件。github
它还生成 public
和src
目录, src
目录是咱们保存源代码的地方。json
请看下面的图片,以及描述的结构:react-router
在这篇文章中,咱们只关注src
目录,src 以外保持不变。app
你可能已经在某些项目的根目录下看到了容器和展现组件之间的分离。个人意思是,在src
中,在 src 目录下有 containers
目录和 components
目录:dom
src ├─ components └─ containers
可是,这种方法有一些问题,以下所示:编辑器
components
挪到 containers
目录下,反之亦然。containers
和 components
目录下来回切换,由于一个独立特性有两种不一样类型的组件是再正常不过的事情了。还有一种方法,在模块内部保存containers
和components
分离:
src └─ User ├─ components └─ containers
上述方法最大限度地减小了在项目树中不一样层级目录切换的问题。然而,它会增长不少噪音。根据你的应用程序有多少模块,你最终会建立几十个containers
和components
目录。
出于这些缘由,当咱们谈论组织目录和文件时,经过展现与容器的概念来拆分组件是可有可无的。 也就是说,除页面外,咱们将把全部组件放在 components
目录下。
即便在目录上区分展现组件和容器组件是没有太多必要的,了解它们之间的差别性依然是有必要的。若是你对这个话题还有疑问,建议阅读这篇文章: Presentational and Container Components
在components
目录中,咱们按模块/功能对文件进行分组。
在用户的增删改查中,咱们只有User
模块,结构是这样的
src └─ components └─ User ├─ Form.jsx └─ List.jsx
当组件由多个文件组成时,咱们将此组件及其文件放在具备相同名称的目录下。 例如:假设有一个包含Form.jsx
样式的Form.css
。 在这种状况下,你的结构以下:
src └─ components └─ User ├─ Form │ ├─ Form.jsx │ └─ Form.css └─ List.jsx
测试文件与被测试的文件保持一致。在上面的例子中,Form.jsx
的测试文件会放在同一个文件夹下而且命名为Form.spec.jsx
除了经过模块拆分组件以外,咱们还在src/components
中包含一个 UI
目录,以保留其中的全部通用组件。
UI 组件是通用的组件,不属于模块。 它们是能够保留在开源库中的组件,由于它们没有来自特定应用程序的任何业务逻辑。 这些组件的示例包括:按钮,输入,复选框,选择,模态框,数据可视化组件等等。
上面咱们看到了如何构建目录并按模块分离咱们的组件。 可是,还有一个问题:如何命名它们?
当咱们谈论命名组件时,它涉及咱们给类或定义组件的常量名称:
class MyComponent extends Component { } const MyComponent () => {};
如上所述,咱们为组件提供的名称应该在应用程序中清晰且独特,以便更容易找到并避免可能的混淆。
当咱们须要使用工具做为React Dev工具进行调试时,以及当应用程序中发生运行时错误时,组件的名称很是方便,错误老是与发生错误的组件名一块儿出现。
咱们采用基于路径的组件命名方式,即根据相对于 components
文件目录的相对路径来命名,若是在此文件夹之外,则使用相对于 src
目录的路径。举个例子,组件的路径若是是 components/User/List.jsx
,那么它就被命名为 UserList
。
当文件位于具备相同名称的组件中时,咱们不须要重复该名称。 也就是说,components/User/Form/Form.jsx
将被命名为UserForm
而不是UserFormForm
。
上面的模式有一些好处,咱们能够在下面看到:
若是编辑器支持模糊搜索,只需搜索名称UserForm
就能够找到正确的文件
若是你想要在目录中搜索文件,能够很容易地经过组件的名字定位到它:
按照该模式,能够始终根据文件的上下文为组件命名。考虑到上面的表单,咱们知道它是一个用户表单,可是因为咱们已经在 User
目录中 ,因此不须要在组件文件名中重复这个单词。所以,咱们只将它命名为Form.jsx
。
我最初使用 React 的时候喜欢用完整的名字来命名文件,可是这样会致使相同的部分重复太屡次,同时引入时的路径太长。来看看这两种方式的区别:
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';
想象一下名称重复十几二十次的样子。
所以,咱们根据文件 的上下文来命名文件,根据组件的相对位置来命名组件是一种更好的方式。
屏幕,顾名思义,就是咱们在应用程序中展现出来的样子。
若是要对一个用户作增删改查的操做,咱们须要有用户列表页面,建立新用户的页面以及编辑已有用户的页面。
咱们将screens
保存在src
根目录中的单独文件夹中,由于它们将根据路由定义而不是模块进行分组:
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;
请注意,咱们将全部页面放在一个目录中,这个目录以路由名称命名,user/ -> User/
。尝试为每一个父级路由创建一个目录,在这个目录中组织子路由。 在这种状况下,咱们建立了User
目录,并将List
页面和Form
页面放入其中。这种方式使你看一眼 url 就可以轻松定位当前路由渲染的页面。
单个页面可用于渲染两条不一样的路线,如上所述,其中包含用于建立和编辑用户的路线。
你可能会注意到全部组件都将Screen
做为其名称的前缀。 当组件位于components
目录以外时,咱们应该根据它到src
文件夹的相对路径来命名。 位于src/screens/User/List.jsx
的组件应命名为ScreensUserList
。
建立 Root.jsx 后,目录的结构以下:
src ├─ components └─ screens ├─ User │ ├─ Form.jsx │ └─ List.jsx └─ Root.jsx
若是你对一个页面长什么样子还有疑问,看看下面的示例,它就是用户表单的页面。
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
src/components
src/components/UI
/user/list
路由地址来讲,咱们会有一个页面在 /src/screens/User/List.jsx
。src/components/User/List.jsx
的组件将被命名为UserList
。 位于src/screens/User/List
的组件将命名为ScreensUserList
src/components/User/List/List.jsx
位置的组件会被命名为 UserList
而不是 UserListList
。你的点赞是我持续分享好东西的动力,欢迎点赞!