21CTO导读:在项目开发中,良好的目录结构是项目成功的开始,看一下本文的前端文件夹设计实践,相信你会有所启发。
我们在平时的应用程序项目开发中,有一项最关键和最具挑战性的就是怎么样设计一个良好且合理的文件夹结构。
这里暂时先不考虑使用微前端,本文使用现有技术加一些可操作的方法来改进项目结构,将来转换升级也会更加容易。
当文件夹的设计合理后,可以使应用更加模块化,使功能之间加上更清晰边界,并且最大限度地减少代码耦合与相关副作用,让代码库的内容让人更加容易理解。
缺省的目录结构
当我们使用任何一种流行的前端框架搭建项目时,框架会缺省创建目录结构。但这些框架组件结构是扁平的,并没有层次结构。
请看如下图示:
以上示例使用的Vue.js框架的目录结构,换成其它框架,比如使用React的目录结构也类似,怎样将文件放入文件夹它们也没有特别的建议。
我们先来讲解一下Vue的目录结构,做一下详细说明:
1)assets目录
这个名字这两年很流行,替换了resources和common等单词。assets目录是整个应用程序的静态资源,包括图片、字体与css文件。
2)components目录
其包含可重用的 Vue 组件。建议采用扁平的层次结构。
3)main.js
该文件作为应用程序的入口点,支持 Vue 进行初始化、插件或附加库的配置。
4)App.vue文件代表应用程序的根组件,充当其它组件的容器以及主模板。
根据经验,对于一个大型项目来说,这种架构很快便会混乱并失控。必须某种模块化才能轻松定位指定文件,并且设置功能之间的边界,并避免组件紧耦合。
将应用程序分解为多个功能
任何大型Web应用程序都会分成多个独立的功能模块,但要标识它们并不总是那么容易,也不怎么直接。
下面,让我们就将一个流行的应用程序按功能分成若干部分作为案例实践。
我们拿 Twitter 网站做样板,它的主页上有不少内容。时间轴是页面的核心,周围有许多功能,如导航、创建推文、具有多个子组件的侧边栏、浮动消息组件等。
如果将构建这些功能的所有组件放在同一个文件夹中是不现实的,就算是放进去,是不可维护的,即使我们使用 IDE 的快速查找选项,想找到其中一个组件也将变得非常困难。
更精细的项目结构
根据以往经验,总结一份更好、更全面的文件夹结构。如下所示:
接下来,我们将目录解释说明如下:
-
components:整个应用程序中使用的所有共享组件。
-
composables:所有共享可组合项。
-
config:应用程序配置文件。
-
features:包含所有应用程序功能。我们将大部分应用程序代码保留在此处。稍后会详细介绍这一点。
-
layouts:页面的不同布局。
-
lib:应用程序中使用的不同第三方库与配置。
-
pages:应用程序的页面。
-
services:共享应用程序服务和提供商。
-
stores:全局项存储。
-
test:与测试相关的模拟器、函数Helper库、实用程序与配置。
-
types:共享 TypeScript 类型定义。
-
utils:共享实用函数库。
在项目根目录下,我们使用如下命令创建相关文件夹目录:
mkdir -p src/{composables,layouts,pages,utils,assets,config,lib,services,test,components,features,stores,types}
需要提请注意的3个重要事项:
1)默认情况下,Pages文件夹以及上下文,使用webpack 或 Vite 等构建工具将创建的Block方面已经进行了某种模块化。将页面放在特别的地方非常有帮助,但其中的逻辑应保持在最低限度。
2)为了更容易维护与可扩展性,我们的目标是将大部分应用程序代码保留在该features文件夹中。每个功能文件夹都包含指定功能,以及特定于域代码。
3)在完美的软件世界中,我们不应该共享组件、可组合项、存储和服务,并且所有内容都将位于相应的功能文件夹内。但是在实际项目中,这是无法避免的,但我们应该提前计划,在向这些文件夹添加内容时需要格外小心。
关于features文件夹
正好之前提到的,我们应该将应用程序按功能分为多个子目录,它们放在 features 文件夹中。
来看如下图示:
-
api:所有的数据获取逻辑都在这里。它们负责将 API 和 UI 解耦。
-
components:具有特定功能的组件。
-
composables:具有特定的可组合项。
-
stores:状态管理代码。从这里能看到,期望并鼓励多个子模块。
-
types:具有特定的 typeScript 类型定义。
-
index.ts:应用程序功能的入口点。它的行为就像功能的公共 API,并且它应只导出对应用程序应该公开的内容。
上面的文件夹和文件,index.ts文件是作为各个功能的公共API。从其它一个域导入某些内容时,需要通过此文件来完成,它可以防止循环依赖,通过它还可以轻松地找到导入源。
以下是两种用法:
# 不好的用法 🚫 🚫 🚫
import { UserProfile } from '@/features/profile/components/UserProfile.vue'
# 好的用法 ✅ ✅ ✅
import { UserProfile } from '@/features/profile'
我们可以使用no-restricted-importsESLint 规则强制执行此模式。
rules: {
'no-restricted-imports': [
'error',
{
patterns: ['@/features/*/*'],
},
],
'import/no-cycle': 'error',
...
}
小结
面向功能的目录架构,是构建复杂项目中有效的,且经过团队充分验证的方法。它允许我们将代码解耦到单独模块中,并且不会随着应用程序变复杂而发生难于扩展实现的问题。
良好的目录结果能够提高代码库的可预测性、减少调试时间并使入门的学习和着手变得更容易,能够改善前端开发体验。
您的应用程序结构是否与上面类似吗?或者在使用其它的好东西?欢迎在本文底部留言。