news 2026/10/4 13:39:39

Vue 3 项目目录结构实战:设计思路与工程化落地指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Vue 3 项目目录结构实战:设计思路与工程化落地指南

刚开始切换 Vue 3 的时候,我真正纠结的其实不是 setup 语法,也不是 ref 和 reactive 到底该用哪个,而是“项目目录到底该怎么摆”。你搜“vue3项目目录结构”,能翻到一大堆模板,但它们往往只在默认脚手架层面展开,真正到了后台管理系统、商城、带权限的复杂项目里,那点默认结构根本不够用。这篇文章是根据我这几年带 Vue 3 项目组的实际经验整理的,不打算教你怎么炫技,而是把目录结构这件事掰开揉碎,从设计思路到具体落地,再到踩坑记录,一次说清楚。无论你是刚用 create-vue 搭完第一个工程,还是正在把老项目从 Vue 2 迁到 Vue 3,下面这些内容应该都能帮上忙。

1. 为什么 Vue 3 项目的目录结构不能照抄 Vue 2

1.1 Composition API 改变了代码的“组织单元”

先说一个很实际的问题:Vue 2 时代,团队写代码其实是按“类型”归位的。组件里固定是 data、methods、computed、watch 这些选项,目录就顺势铺成 components、views、router、store、utils 这种大分类。那时候一个功能模块的代码,会被平均分摊到这些目录里,比如订单功能,订单表格组件放在 components,订单页面放在 views,订单请求放在 api 或 utils,订单状态放在 store。这种目录对小型项目还能忍受,但项目一过 20 个页面就会产生一种典型症状:改一个订单状态流转的需求,你需要在五六个目录之间来回跳,稍不注意就漏改。

Vue 3 的 Composition API 把代码组织的最小单位从“选项”变成了“逻辑片段”。同一个功能里,状态、计算属性、方法可以通过 setup 组合在一起,还能抽成独立的 useXxx 函数。这就让目录设计多了一条路:按业务域组织。比如你的订单模块可以直接在 src/features/order 下放 order.ts 或 order-composable,内含订单状态、下单逻辑、价格计算,而不是把这段逻辑强行拆进 utils 和 store。这个变化听起来不大,但团队一旦习惯按业务域组织,改需求的时候会很舒服——一个功能相关的文件,基本上在同一个目录里就能找齐。

1.2 好目录结构的验收标准:可定位、可约束、可扩展

我见过太多团队,文件夹名字起得花里胡哨,最后连自己都找不到文件。判断一套 Vue 3 目录结构好不好,其实不用看多少理论,就看三条标准:

第一,可定位。新同事问“商品列表接口在哪个文件”,答案应该是唯一的,而不是“可能在 api 里,也可能在 service 里,你看哪个顺眼”。一个目录如果出现了两个名字含义重叠的目录,比如同时有 services 和 api,那就是隐患。

第二,可约束。目录结构本质上是对团队的约束,它得能拦住坏味道。比如组件里永远不能直接写 axios 请求,这个约束怎么落实?靠 review 和自觉效果不稳定,更靠谱的做法是在目录上体现:明确划分 api 层目录,所有网络请求只能留在 api 目录里的文件,这样 code review 时一眼就能看出谁越界了。

第三,可扩展。新增一个功能模块时,你应该只需要“新建一个文件夹,里面放视图、composable、局部组件、接口文件”,而不是在十几个平级目录里各加一个文件。这条标准直接决定了你在设计目录时要不要引入业务域的概念。

1.3 基于类型与基于业务的两种组织方式

很多初学者纠结的问题是:我到底应该按“类型”建目录,还是按“业务”建目录?我的回答是,绝大多数项目应该用“混合式”:顶层按类型,模块内按业务。

顶层按类型,指的是 src 下保留 views、components、composables、api、stores、router、utils、styles、types 这些约定俗成的类型目录。这样做的好处是心智负担低,Vue 生态的工具链、脚手架和大部分开源项目都默认这种结构,团队招人进来上手快。模块内按业务,指的是在这些类型目录内部,不要把所有文件平铺,而是按业务模块再分一层。比如 views 下是 views/admin、views/order、views/user,api 下同样对应 api/admin、api/order、api/user,而不是 views 下堆 80 个 index.vue。

这两种方式没有绝对的对错,真正不合适的是全按类型把文件堆成一个大平层,或者全按业务把所有东西都塞进一个模块目录里导致全局共享的组件和逻辑没法复用。后面我会详细说说具体怎么落地。

2. 搭建 Vue 3 + TypeScript 项目的标准目录骨架

2.1 用 create-vue 初始化后,默认结构到底给了我们什么

现在创建 Vue 3 项目,我推荐直接用官方脚手架,新版本默认已经包含了 Vite、TypeScript、ESLint、Prettier 这些基础设施的选择项。初始化命令很简单:

npm create vue@latest my-vue3-app

按提示勾选 TypeScript、Router、Pinia,生成出来的基础结构大致是这样:

my-vue3-app/ ├── public/ │ └── favicon.ico ├── src/ │ ├── assets/ │ │ └── base.css │ ├── components/ │ │ └── TheWelcome.vue │ ├── router/ │ │ └── index.ts │ ├── stores/ │ │ └── counter.ts │ ├── views/ │ │ ├── HomeView.vue │ │ └── AboutView.vue │ ├── App.vue │ └── main.ts ├── .env.development ├── .env.production ├── .eslintrc.cjs ├── .gitignore ├── .prettierrc.json ├── index.html ├── package.json ├── tsconfig.app.json ├── tsconfig.json ├── vite.config.ts └── README.md

官方脚手架给的是一个“最小可用”的结构,它的意义不是让你原封不动拿来开发,而是让你看清 Vue 3 项目需要管理哪些层面的东西。src 之外有配置文件,src 之内分好了静态资源、组件、路由、状态、视图这几块。你在这套骨架上继续发育,而不是推翻重来。

2.2 根目录配置文件各自扮演什么角色

很多人建完项目就直奔 src,根目录那一堆配置看都不看,这是不对的。先理清根目录几个关键文件:

package.json 不用多说,它是项目的身份证明。要留意其中 type 字段是否为 module,Vue 3 + Vite 的现代项目基本都是 ESM,这决定了导入导出语法和配置文件本身要用 import/export 而不是 require。

vite.config.ts 是构建与开发服务器的总控。目录结构相关的核心配置就在这里,比如路径别名 alias、开发环境代理 proxy、CSS 预处理器配置。后面几节我会专门讲别名配置,这里先记住:凡是会影响“文件从哪里来、资源怎么被加载”的规则,几乎都在这个文件里。

tsconfig.json 和它的拆分文件负责 TypeScript 编译选项。Vue 3 的 create-vue 模板把 tsconfig 拆成 tsconfig.app.json、tsconfig.node.json 等几份,核心原因是 IDE、构建脚本和 Node 侧代码需要的编译目标不同。你在这个文件里配置 paths 路径别名时,要特别小心别只改 vite.config.ts,两边不一致就会出现“Vite 构建没问题,编辑器里却一路红线”的经典事故。

.env.development 和 .env.production 是环境变量文件。值得注意的坑是:Vite 默认只会把以 VITE_ 开头的变量暴露给前端代码,比如 VITE_API_BASE_URL。不要指望在这里定义一堆自定义变量能被业务代码读到,命名规则从一开始就要统一。

2.3 public 与 src/assets 的分工,别再放错静态资源

我接手过的项目里,最常见的一个目录设计错误就是所有静态资源都往 public 里扔。public 目录的产物是“原样拷贝”,里面的文件不会被 Vite 处理和改名,路径是写死的绝对路径。而 src/assets 里的资源会被打包器读取,经过压缩、指纹命名、按需引入等处理,引用方式也应该是 import 或 new URL 的形式。

实际使用中,我的规则很简单:只有那些不需要参与构建逻辑的资源才放 public,比如 favicon、roubust 第三方SDK的完整离线包、某些不能改路径的静态文件;而图片、图标、样式图片这些会被组件引用的资源,全部放进 src/assets。尤其是离线地图打点这类场景,如果你要把离线瓦片资源放到 public 下,注意目录级别要深一点,别和页面文件混在一起,否则构建后文件数量一多,线上加载路径非常容易错乱。

这里再插一个经验:assets 目录也不要单靠 css 和 img 两级平铺,按模块分一层会更好,比如 assets/img/order、assets/img/user。否则几十个页面共用一套图片资源时,你根本不知道某张图是被谁在引用,想清理都不敢动。

3. src 内部的深度拆解:每个核心目录怎么设计

3.1 views 与 components 的边界到底怎么划

src 内部最重要的边界,是 views 和 components 这两块。我的划分标准很简单:views 里的文件对应路由,一个路由至少对应一个页面级组件;components 里的文件是被页面复用的功能单元,本身不直接出现在路由表里。

但在实际项目里,这里有个容易被忽视的细节。Views 目录里不只是放一个 index.vue,因为一个路由页面的内部复杂度往往很高。比如后台管理系统的用户列表页,有搜索表单、工具栏、表格、分页器、新增编辑弹窗,如果全写进一个 index.vue,文件会膨胀到几百行。我的做法是在 views/user 下这样组织:

src/views/user/ ├── index.vue # 页面主组件,负责组装 ├── UserSearch.vue # 搜索表单 ├── UserTable.vue # 表格 ├── UserDialog.vue # 新增/编辑弹窗 ├── useUserList.ts # 本页面业务逻辑的 composable └── user.api.ts # 本页面专属的接口请求

注意 search、table、dialog 这些局部组件没有丢到全局 components,因为它们很可能只在当前页面使用。全局 components 目录只放真正会被两个以上页面复用的东西,比如上传组件、富文本组件、通用弹窗。这个边界能帮你避免 components 变成一个装满“孤儿组件”的仓库。

3.2 composables/:把逻辑从组件里“搬出来”的正确姿势

Composition API 带来最大的目录变化,就是 composables(也有团队叫 hooks)目录。在 Vue 2 里,复用逻辑靠 mixin,但 mixin 最大的问题是来源不明,你根本不知道一个计算属性是从哪个 mixin 混进来的。Vue 3 的 composable 把逻辑封装成普通函数,谁用了它,它就存在在谁的作用域里,直观且好排查。

composables 目录的组织,我建议先按功能域拆分,再按领域拆分。比如:

src/composables/ ├── usePagination.ts # 分页通用逻辑 ├── useTableState.ts # 表格加载、刷新、选中的通用逻辑 ├── useFormDialog.ts # 弹窗打开关闭和表单初始化的通用逻辑 ├── order/ │ ├── useOrderCalc.ts # 订单价格计算 │ └── useOrderStatus.ts # 订单状态流转 └── user/ └── useUserInfo.ts # 用户信息获取与缓存

放在 composables 里的函数,一定要遵守 useXxx 命名规范,还要有明确的输入输出,不要偷偷修改外部全局状态。我常用的一个心法是:如果一个 function 里读写了很多组件外部的变量,又没有通过参数传入,那它就还不算合格的 composable,应该改造成纯函数再提升到 utils。

这里要专门提醒一个点:composables 目录不要藏“单例状态”。全局状态的管理应该归 Pinia store 管,composable 更适合处理与视图逻辑绑定的临时状态和副作用。两条线混了以后,会出现“同一个用户信息,一会儿从 store 读,一会儿从 useUserInfo 初始化”的混乱局面。

3.3 stores/:Pinia 来了,目录怎么跟上前端需求变化

Pinia 已经成为 Vue 3 官方推荐的状态管理方案,它对应的目录也从 Vue 2 的 store/modules 变成了语义更清晰的 stores。我建目录时习惯按业务模块拆分,一个模块一个文件,命名上使用组合式风格而不是选项式风格:

// src/stores/order.ts import { defineStore } from 'pinia' import { ref, computed } from 'vue' import { fetchOrderList } from '@/api/order' export const useOrderStore = defineStore('order', () => { const orderList = ref([]) const loading = ref(false) const total = computed(() => orderList.value.length) async function loadOrderList() { loading.value = true try { orderList.value = await fetchOrderList() } finally { loading.value = false } } return { orderList, loading, total, loadOrderList } })

组合式写法看起来像普通 composable,但它依然是一个 store,在 Pinia 里通过 id 区分。store 文件内部也要克制:不要塞所有业务字段,只放真正需要跨组件共享的“全局状态”;组件内部自己管得住的临时状态,留在组件里就好。

一个额外的组织技巧是,如果 store 文件比较大,可以在 stores 目录下建子目录并按模块拆分,比如 stores/user、stores/cart、stores/order。但对应关系要跟命名跟紧,不要让 stores 里出现一个谁都找不到归属的文件。

3.4 api/ 层:把网络请求集中起来,别让 fetch 满天飞

如果你去翻那些目录混乱的 Vue 3 项目,大概率能看到 axios/fetch 调用散落在 views 和 composables 里。短时间看是省事,但接口一变,你要搜索所有字符串去改路径,很容易漏。更严重的是,你没有办法统一处理错误、取消重复请求、注入 token。

所以我在项目里一定会让 api 作为一个独立的层目录存在。基本结构是这样的:

src/api/ ├── http.ts # axios 实例、拦截器、基础请求封装 ├── types.ts # 与后端交互的通用类型,如分页结构、响应结构 ├── user.ts # 用户模块接口 ├── product.ts # 商品模块接口 └── order.ts # 订单模块接口

http.ts 是唯一的 axios 实例出口,所有请求都通过这个实例发出,这样 token 注入、错误码提示、接口路径拼接都只在一处处理。api 目录下的业务模块文件里只导出函数,不写组件逻辑:

// src/api/order.ts import http from './http' import type { OrderItem, OrderQuery } from './types' export function fetchOrderList(params: OrderQuery) { return http.get<OrderItem[]>('/order/list', { params }) }

api 目录的设计,等于给团队立了一条硬性规矩:组件里不允许出现裸 axios 调用。规矩不需要挂在墙上,它天然存在于目录结构里,code review 时只要看到某组件 import 了 axios,就已经算违规了。

3.5 router/:路由文件怎么拆,懒加载怎么配

路由目录虽然一般不起眼,但它和目录结构是强相关的——因为路由是页面目录的入口。Vue 3 项目里,路由文件不应该是一个只增不删的 index.ts,尤其当项目页面超过 50 个时,一个 flat 的路由表会变成“怎么都找不到要改哪一段”的灾难。

我常用的路由组织方式有两种。项目模块边界清晰时,按模块拆分路由定义文件:

src/router/ ├── index.ts # 创建 router 实例、配置全局守卫 ├── modules/ │ ├── dashboard.ts │ ├── order.ts │ └── user.ts └── routes.ts # 汇总所有模块路由

每个模块的路由文件内部都应该使用动态导入实现懒加载:

// src/router/modules/order.ts const orderRoutes = [ { path: '/order', component: () => import('@/views/order/index.vue'), children: [ { path: 'detail/:id', component: () => import('@/views/order/detail.vue'), meta: { title: '订单详情' } } ] } ] export default orderRoutes

懒加载最大的意义不是“省代码”,而是按页面拆分包,首屏只加载当前路由需要的 JS。这对中大型项目体验提升很明显。还有一点很多人会忽略:meta 字段要提前规划好,比如 title、requiresAuth、permission。后台管理系统往往会围绕路由 meta 来做菜单生成和权限控制,如果一开始没设计,后面加权限就跟在没打地基的楼上盖两层一样难受。

3.6 utils、styles 与 types 的“三不管地带”怎么处理

views、components、composables、stores、api、router 这些目录都很明确,但总有文件不知道该放哪。我的处理方式是把它们压进三个兜底目录,并且给每个目录定好准入标准。

utils 目录只收纯函数,或者说不依赖 Vue 运行时的工具函数。格式格式化、日期处理、树结构转换、浏览器 cookie 操作这类。utils 文件要保证是可以被单独测试的,如果你在一个 utils 文件里 import 了某个 store 或某个组件,那它就不是 utils,而是业务逻辑。

styles 目录要区分全局样式和模块样式。全局变量、混入、基础样式放 styles;页面和组件的局部样式,用 scoped 写在对应组件里。如果你用的是 SCSS,styles 下建议放 variables.scss、mixins.scss、reset.scss 这些。注意:全局样式文件不要到处 import,更推荐在 vite.config.ts 里配置 css.preprocessorOptions 实现自动注入。

types 目录是 TypeScript 项目特有的一层,用来放全局共享的类型定义。但我要提醒一点,types 目录不要变成一个“所有类型都往里丢”的垃圾桶。常见做法是:跟 api 模块强相关的类型放在 api 模块文件内部,比如 OrderItem、OrderQuery;跨多条业务线共享的类型才提升到 types 目录,比如分页结构 PageResult、统一响应结构 ApiResponse。两者边界清晰,才不会出现“为了找某个接口的返回类型,得去 types 目录翻半小时”的尴尬。

4. 高频实战场景下的目录落地:后台管理、商城、前后端联调

4.1 后台管理系统:布局、权限、动态路由的目录配套

后台管理系统应该是 Vue 3 中需求量最大的项目类型。它的目录设计比普通网站多出两个维度:布局和权限。我常用的后台目录框架大概是:

src/ ├── api/ │ ├── system/ # 用户、角色、菜单等系统接口 │ └── business/ # 具体业务模块接口 ├── layouts/ │ ├── index.vue # 整体布局骨架 │ ├── Sidebar/ │ ├── Navbar/ │ └── TabsView/ # 多页签 ├── router/ │ └── modules/ # 按权限模块拆路由 ├── stores/ │ ├── user.ts # 用户信息与权限点 │ ├── app.ts # 侧边栏折叠、页签状态等 UI 状态 │ └── permission.ts # 可访问路由列表 ├── views/ │ ├── dashboard/ │ ├── system/user/ │ ├── system/role/ │ └── business/... ├── components/ │ ├── TablePro/ │ ├── SearchForm/ │ ├── DialogForm/ │ └── SvgIcon/ └── composables/ ├── useTablePro.ts └── usePagination.ts

layouts 目录在后台管理里非常关键,它负责把导航侧栏、顶栏、多页签和路由出口组合在一起。视图组件不直接写整体框架,而是嵌套在布局里。动态路由是另一个搭配点:后端根据用户权限返回菜单路由,前端在路由守卫里用 router.addRoute 动态注册。目录要注意的是,动态加载的视图路径不要写死字符串去拼,容易导致构建时找不到组件,最好在目录层级上与路由模块一一对应。

4.2 商城类项目:业务域边界怎么划分

商城类项目和后台管理比,页面复杂度不一定更高,但业务域特别清楚:商品、购物车、订单、支付、用户中心。这种项目我在顶层类型目录的基础上,会把业务域区分得更明显。以订单模块为例:

src/ ├── api/ │ ├── product.ts │ ├── cart.ts │ ├── order.ts │ └── payment.ts ├── stores/ │ ├── cart.ts │ ├── product.ts │ └── order.ts ├── views/ │ ├── product/ │ ├── cart/ │ ├── checkout/ │ └── order/ ├── components/ │ ├── product/ │ ├── cart/ │ └── common/ ├── composables/ │ ├── product/ │ ├── cart/ │ └── order/

这样划分之后,每个业务的改动都只涉及它对应的一组目录。比如支付成功后更新订单状态,涉及的就是 stores/order.ts、api/order.ts、views/order/ 和几个 composable,不会误伤其他模块。

商城还有一个特点是要适配多端或多种场景。比如离线地图可打点,这种功能会涉及地图 SDK、打点数据源和业务组件,我建议在 components 下单独建一个 map 子目录,里面放 MapContainer、MapMarker、MapPopup 等,然后通过 composables 把地图初始化的生命周期逻辑抽出来。不要把地图相关代码散落到订单、配送各页面里,否则地图升级 SDK 时,你会为“每个页面都有 initMap”付出代价。

4.3 与 FastAPI 或 Java 后端配合时的目录约定

目录结构不只和前端自己有关,前后端的配合方式也会反推目录设计。比如后端用的是 FastAPI,它的路由结构常常按模块组织为 user、order、product,前端 api 目录也按同样的模块命名,联调时两边对文件的成本就低很多。

更进一步,前端 api 层和类型文件要与后端的接口 schema 对齐。FastAPI 通常有 Pydantic 模型,前端可以维护一份与之对应的 TypeScript 类型,放在 api 或 types 目录里。后端返回体如果有一个约定的统一结构,比如:

// src/api/types.ts export interface ApiResponse<T> { code: number message: string data: T }

前端所有请求函数都基于这个结构解包,后端换字段名时,前端只改对应的一个类型定义即可。这里还有个实操经验:联调阶段接口地址频繁变更,建议把 baseURL 放到环境变量里,同时在 vite.config.ts 配置 proxy 指向本地后端端口。目录上不要把后端地址写进代码,写进配置,不然切环境时要全项目搜“localhost”。

5. 我在 Vue 3 目录结构上踩过的坑和调整记录

5.1 到处都是绝对路径和深层相对路径

早期做项目时,大家图省事,import 路径直接写 ../../../../utils/format。一开始觉得没什么,后来重构目录,把 utils 文件夹挪了一个层级,结果全局搜索替换花了一个小时,还漏了好几个没改到,构建直接报错。这个坑的本质是:目录结构没有在 import 层形成“稳定接口”。

我现在的做法是在 vite.config.ts 和 tsconfig 里统一配置路径别名:

// vite.config.ts import { fileURLToPath, URL } from 'node:url' import { defineConfig } from 'vite' export default defineConfig({ resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } } })
// tsconfig.app.json { "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["src/*"] } } }

之后所有组件 import 都用 @/ 开头,比如 import { fetchOrderList } from '@/api/order'。这样一来,即使整个 src 内部再调整层级,只要别名不变,业务代码就不用改。目录结构有了喘息空间。

5.2 循环依赖的典型场景与解决办法

目录分得再清,也架不住互相引用。我遇到过两个特别典型的循环依赖场景。

第一个场景是 api 文件和 store 文件互相 import。比如 order.ts 这个 api 模块里导入 useOrderStore 来获取用户 token,而 stores/order.ts 里又导入 api/order.ts 来拉列表。运行时可能表现为 store 初始化时 undefined,或者请求被拦截器卡住。解决办法是把需要 token 的逻辑从 api 层拿走,token 应该在 http 拦截器里统一读取并注入,而 http.ts 只依赖一个简单的 token 存储模块,不依赖任何业务 store。

第二个场景是两个组件互相引用,最常见是父子弹窗组件互相传引用。目录上要避免它们互相 import,做法是把被共享的逻辑抽到 composable 或 store 中,让组件之间的依赖变浅。排查循环依赖有一个很实用的命令:运行vite build时如果出现“Circular dependency”提示,立刻先检查这几个文件的 import 链,不要等到线上出问题再回头找。

5.3 目录过扁与过深,两种极端都要避免

有些项目特别“平”,components 下直接放 50 个组件文件,api 下直接放 20 个 ts 文件。这种结构表面简单,但一旦文件数量变大,你打开目录要滚动很久,而且命名冲突概率很高。反过来的极端是目录嵌套过深,有人能建出 src/views/business/order/list/detail/components/ 这种四层往上的路径,单看路径脑袋都大。

我的经验法则有三条。第一,单目录内文件数量超过 15 个到 20 个就要考虑按子模块拆分。第二,从 src 开始到最深层文件,目录层级建议控制在四层以内,超过就说明嵌套过度。第三,每个目录尽量有单一职责,目录名和内容要一致,宁可多建一个 index.ts 做中转,也不要让目录像个杂物箱。

5.4 TS 项目中的类型目录处理和路径别名报错

TypeScript 引入后,目录结构会多出很多和类型相关的报错。常见的一种是“找不到模块或其相应的类型声明”,这往往不是因为类型不存在,而是 tsconfig 里的 paths 和 vite 的 alias 没有保持同步。我在几个若依 Vue3 老项目接新需求时也遇到过类似 ts 报错,最后排查下来基本都是 tsconfig 没有正确配置 baseUrl 或 paths。

另一个容易忽略的点是 import type 的使用。在开启了 verbatimModuleSyntax 或严格模式的项目里,纯类型导入必须用 import type,否则构建阶段会报错。这也算目录结构配套的一部分:类型定义放在 types 目录里,导入时明确区分类型和值,编译期才能干净。

5.5 一次渐进式目录重构的操作记录

最后分享一次真实的重构操作。一个 Vue 3 + TS 的后台项目,初期图快把所有东西按类型堆在一起,views 下有 60 个页面文件,components 下堆了 30 个。我带着组员做了一次渐进式重构,步骤可以给正在发愁的人参考:

第一步,先列模块清单,按业务划成 dashboard、system、order、user、report 五个一级模块。第二步,把 api 目录按模块拆开,逐个移动文件并修正 import 路径;这个过程里只做移动,不改业务代码,风险很小。第三步,把 views 下同模块的页面归到一个目录,同时把页面专属的局部组件从全局 components 移进 views 对应模块下。第四步,把 composables 里已经积累的几个 useXxx 归类到对应模块。第五步,删除没有引用方的孤儿文件,跑一遍全量构建和回归测试。

整个重构拆成了三轮提交,每轮都能构建通过,团队没有因为“大重构”停工。这套思路最重要的是:目录结构的变化可以像功能迭代一样小步推进,不要指望一次 git commit 全改完。

6. 让目录结构真正提升效率的工具与工作流

6.1 Vite 别名与 tsconfig 的同步配置

配置路径别名是目录结构最基础的建设,值得花几分钟把配置写对。除了前面说的 @ 指向 src,还可以针对某几个大目录单独起别名,比如:

resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)), '@api': fileURLToPath(new URL('./src/api', import.meta.url)), '@views': fileURLToPath(new URL('./src/views', import.meta.url)) } }

注意,每个别名都要在 tsconfig 的 paths 里同步一份。如果觉得维护麻烦,只用 @ 一个别名也完全够用,关键是一致性。

6.2 目录聚合导出 index.ts:小心使用

很多项目喜欢在每个目录下放 index.ts 做聚合导出,比如 components/index.ts 统一 export 所有组件,api/index.ts 统一 export 所有接口。好处是 import 路径精简,坏处是文件一多,会形成巨大的引用链,构建时可能连带加载用不到的文件,还更容易触发循环依赖。

我的建议是:不要盲目做全目录聚合。对于 api 和 composables 这种“按需导入更清晰”的目录,让每个文件独立导出;对于组件库类的目录,可以用 index.ts 做受控导出,但宁可显式列出导出列表,也不要写成动态遍历后全部 export 的模式。

6.3 按目录约定自动生成路由或菜单

当目录结构稳定以后,可以进一步利用“约定优于配置”来做自动化。比如你规定 views 下的每个一级目录对应一个路由模块,那么就能写一个脚本扫描目录自动生成路由表。或者用 Vite 插件支持文件路由模式,路径直接映射为 URL——像 pages 目录下 product/list.vue 自动对应 /product/list。

这类方案能大幅减少手工维护路由表的成本,但我的建议是不要把魔法做得太深。目录约定和路由映射之间存在隐式规则,新人不熟悉时会踩坑。小到中项目手工维护路由表完全够用;项目大到几十个页面时,自动化才凸显价值。

6.4 页面到代码的快速定位,靠的首先是好目录

热词里有人问“vue3 可以根据页面快速定位到代码的插件”,我理解这种需求,很多开发者在浏览器里看到一个页面,想知道它对应哪个组件文件。Vue DevTools 在 Vue 3 里能很好地支持 component tree 定位,编辑器的插件也可以做 Open in Editor。但对大项目来说,最稳定的做法反而简单:目录结构和路由命名保持一致,页面路径就是文件路径,@/views 下搜一下就定位到了。

比如商品详情页是 /product/detail/1,那它的视图文件大概率就是 src/views/product/detail.vue,接口在 src/api/product.ts,状态在 src/stores/product.ts。这种“不看插件也能猜到文件位置”的流畅感,才是目录结构带来的真正效率。

最后再分享一个经验:目录结构不是一次定型的东西,它会随着项目规模、团队构成和业务演进不断调整。但只要守住每个目录的职责边界、维护好路径别名、让文件位置能够被稳定预测,你在 Vue 3 项目里的大多数目录烦恼都会消失。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/4 13:39:03

Hermes Agent自进化机制核心:MCE公式原理解析与工程调优

1. 这不是数学课&#xff0c;而是一次对智能体底层生长逻辑的解剖“从一个公式切入回看 Hermes Agent 的自进化机制”——这句话乍看像学术论文标题&#xff0c;实则藏着当前智能体开发圈最硬核的一次实践反思。我接触 Hermes Agent 是在去年底&#xff0c;当时它刚发布 v0.21&…

作者头像 李华
网站建设 2026/10/4 13:38:29

CDS Table Function 的真正优势,从 ABAP CDS 能力边界走向 SAP HANA 原生计算

在实际的 SAP S/4HANA 开发项目里,ABAP CDS 很容易给开发人员一种感觉,只要数据模型设计得足够好,似乎所有读取、关联、聚合和计算需求都可以一直写在 CDS View Entity 里面。 这种感觉在大部分业务场景中并没有问题。 普通的 Join、Association、字段计算、聚合、参数化查…

作者头像 李华
网站建设 2026/10/4 13:36:52

算法与数据结构入门:复杂度、数组与链表

摘要 算法和数据结构是编程能力的基础。很多性能问题并不是由语言本身造成的&#xff0c;而是因为没有根据数据规模选择合适的数据结构&#xff0c;或者忽略了算法的时间和空间复杂度。 本文从复杂度分析开始&#xff0c;介绍数组、动态数组和链表的基本结构、访问与插入特点…

作者头像 李华
网站建设 2026/10/4 13:34:48

【工程物理基础专栏 01】体积与重量:从概念混淆到公式推导,一文打通底层逻辑(附实例 + 代码)

专栏定位:从工程与物理的基础量纲出发,拆解每一个常用公式的来龙去脉,兼顾入门易懂与底层深度。适合学生、机械 / 土木工程师、电商物流从业者等所有需要和体积、重量打交道的读者。 本篇是专栏第 1 篇,我们从最容易混淆的 “重量” 概念入手,一步步推导体积与质量、重力的…

作者头像 李华
网站建设 2026/10/4 13:33:44

3 步接入 Higress Nacos 服务发现:微服务动态路由与灰度发布实战

3 步接入 Higress Nacos 服务发现&#xff1a;微服务动态路由与灰度发布实战 【免费下载链接】higress &#x1f916; AI Gateway | AI Native API Gateway 项目地址: https://gitcode.com/GitHub_Trending/hi/higress 凌晨扩容后你下线了两个服务副本&#xff0c;网关的…

作者头像 李华