Vue3 项目的基本架构解读
目录
一. Vue项目构成及核心文件解读
1.1 assest 静态资源文件
1.2 utils 工具包
1.3 api,controller,http 包
1.4 view,page 包的页面组件
1.5 component 公共组件、UI组件
1.6 router/index.js——路由配置
1.7 store/index.js——状态管理
1.8 app.vue
1.9 main.js 应用初始化脚本
1.10 HTML 应用入口容器文件
二. Vue 项目启动文件加载流程及依赖分析
在接手一个Vue项目时,合理的项目结构可能让我们非常迅速地对一个项目进行了解;此外,一个和良好的项目结构,对于项目后期的维护性和扩展性都是极有帮助的。下面我们就一起来看一个相对完整的Vue项目吧!
一. Vue项目构成及核心文件解读
如上图所示,就是一个相对完整的Vue项目,大致可以分为10部分,我都一一对应做出了标注。
下面在介绍的时候,不会按照图片标注的顺序来进行说明,我们先从代码、图片文件介绍,再介绍项目结构、配置文件,方便同学们更好理解。
1.1 assest 静态资源文件
作用:存放静态资源文件
这个应该是最好理解的了,里面通常会存放一些项目图片,网站图标,CSS样式文件,字体文件等。如果将文件夹展开,通常是下面这样,assest 大文件夹包含四个小文件夹,每个文件夹下存放对应的资源文件,这里就不展开说了,很好理解。
需要注意的一点是,在有一些中型以及大型项目中,由图片、图标、CSS、字体设置文件由于数量较多,会分为四个顶层文件夹来存储,就不再全部或部分存储到 assest 文件夹中啦,这一点小伙伴们可以注意一下,以后遇到了其他的Vue项目发现不一样也不用惊奇,这四类文件位置放哪都可以,但一般要么放一起,要么分四个放,要么部分放一起,都无所谓的。
1.2 utils 工具包
作用:存放工具类JS文件
这个基本上也不用怎么说,就是存放一些提前定义好的工具类JS文件,JS方法,类似于spring boot 项目的 utils 工具包,存放各种定义好的Java方法。
下面是我已经写好的一些JS文件,可以简单看下,从名字基本就能看出来是干什么用的,Date就是处理时间的,math是数学运算的......
1.3 api,controller,http 包
作用:存放与后端交互的请求函数,接口规范JS文件
向后端发送请求时,通常需要设置请求参数、请求方式、请求地址,这些通常都会在JS文件中提前定义好。其实之所以叫 api,controller,http 是因为在不同的项目中,或因个人习惯的不同,这个 request 请求包通常会有不同的命名方式,但比较常见的是以上三种,
如下图所示,我简单展示该包下的文件。
然后我打开 baseCode.js 文件,小伙伴们可以简单看一下,不难发现,里面都是已经提前定义的接口规范,例如方法名称、参数值、参数处理、URL地址、HTTP请求类型等,然后使用 export 导出,这样就可以在其它Vue组件中导入使用,类似于Java后端的 import 导包,这种做法极大的提高了代码的可复用性,只定义一个HTTP函数,在项目多处都可以导入使用,极大程度上做到了我们开发中常说的低耦合!
import request from '@/utils/request'
import PromiseCache from '@/utils/PromiseCache'const queryLikeCache = new PromiseCache()
const queryInCache = new PromiseCache()export function queryLike(codeType, query, otherConditions) {const key = JSON.stringify({ codeType, query, otherConditions })return queryLikeCache.getCached(key, () => {return request({url: '/baseCode/queryLike',method: 'post',data: { codeType, query, otherConditions }})})
}export function queryIn(codeType, codes) {const key = JSON.stringify({ codeType, codes })return queryInCache.getCached(key, () => {return request({url: '/baseCode/queryIn',method: 'post',data: { codeType, codes }})})
}
在以往的 JavaScript 代码中,如下代码所示,通常都是直接在 JavaScript 代码中使用 Ajax、Axios 发送 HTTP 请求,这种做法就显得不够优雅,如下,envConfig.api.ip + config.backend.companyInfo 是已经提前封装在其他配置文件中了,所以显得简洁一些,如果没有封装,就会变得非常杂乱。
$http.post(envConfig.api.ip + config.backend.companyInfo, {"searchKey": $scope.clientData.insuredName
}).then(function(res){if(res.data.VerifyResult=='1'){$scope.clientData.smallMicroBusinesses='1';if($scope.companyData){$scope.companyData.smallMicroBusinesses = '1';}parentScope.proposal.prpTmainVo.smallMicroBusinesses='1';}else {$scope.clientData.smallMicroBusinesses='0';if($scope.companyData){$scope.companyData.smallMicroBusinesses = '0';}parentScope.proposal.prpTmainVo.smallMicroBusinesses='0';}
});
当我们使用了 export 导出方法和 import 导入之后,代码就可以变成下面这个样子。
首先第一步:导包,将提前定义好的 HTTP 规范函数导进来;
其次第二步:编写HTML这种页面元素,绑定 button1 单击函数,传递查询参数;
最后第三步:在 vue 的 method 列表中添加 button1 函数,在函数中注解调用已经导入进来的方法 queryLike,传递查询参数;
如此再来看,在整个文件中,几乎看不出来有调用 HTTP 的痕迹,全是纯代码逻辑编写,代码之间的耦合度也降低了,非常的优雅!
1. 导包部分
import { queryLike } from '@/api/common/baseCode'2. 页面部分
<el-button :disabled="editDisableds" @click="button12()">)</el-button>3. vue中的 method 方法
methods: {button1() {queryLike('feeMethod', this.intermediaryjg.feeMethod, {}).then((response) => {for (const item of response.data) {console.log(item.cname)this.intermediaryjg.formula = this.intermediaryjg.formula + '' + item.cname}})},
}
1.4 view,page 包的页面组件
作用:存放向用户实际展示的页面级别Vue组件(通常与路由对应)
简单来讲,就是之前的 .HTML 文件,有一点不同的是,在 vue 项目中,通常不会在使用 .html 文件,而是使用 .vue 文件,而且通常会搭配 elemenUI 组件一同使用,这里没什么好解释的。
此外,我这里将软件包定义为 view ,在一些其他项目中,因为项目开发习惯的不同,有些人会将软件包定义为 page,所以同学们下次见到不必大惊小怪,二者一般情况下都可以用来存放页面文件。
在这个软件包下,通常不同的模块还会定义子包,比如用户模块的页面文件都放在 user 包下;orders 订单模块的页面文件都放在 orders 包下;后台管理页面文件都放在 admin 包下;
1.5 component 公共组件、UI组件
作用:存放可复用的 Vue 组件
组件是 Vue 项目中一个非常重要的组成部分,广义上来说,任何一个定义的以 .vue 结尾的文件,都可以被看作是一个 Vue 组件,只需要在文件中使用如下语法,就可以把当前 .vue 文件作为一个组件使用导入到其他文件中或被其组件嵌套使用。
export default {// 1. 组件标识:当前要导出的组件名称,自定义名称,便于调试和递归name: 'UserCard',// 2. 数据管理:内部用来定义双向绑定的数据data() {return {isExpanded: false}},// 3. 方法:就是自定义的方法函数,都写在 methods 里面methods: {toggleExpand() {this.isExpanded = !this.isExpanded}},// 4. 组件注册:组件之间可以相互调用,import 导入后就在这里面声明components: {UserAvatar,UserInfo}// 5. 属性接收 (Props),可以用于验证、校验数据是否输入正确props: {user: {type: Object,required: true}},
}
在 Vue 项目中,组件也是有多种多样的,我就列举了两类比较常见的,一个是公共组件,一个是UI组件。
其实二者区别不大,指示作用不太相同,公共组件类似于Java后端代码的 common 公共包,里面定义一些比较通用的组件;UI组件也是一样,就是提前定义好的一些界面样式,有时候很多网页结构都是非常相似的,这个时候我们就可以将网页结构整体提出来,然后在每一个 .vue 文件中进行导入直接复用,提高了代码的质量,降低了冗余度。
如下图,就是组件包展开的样子,一般可以定义两个子包,layout 通常存放公共组件;ui 就用来存放UI组件。
1.6 router/index.js——路由配置
作用:存放路由配置(路径与组件的映射),管理页面跳转和路由守卫。
该文件夹下通常只含有一个核心文件 index.js ,index.js 文件中会定义所有的页面组件和路径的映射关系。
注意!!!这里所指的页面组件就是直接展示给用户浏览的页面,基本上就是 view、page包下的页面组件,而公共组件包是不映射的,具体是否映射见下表格。
需要路由定义的组件 | 不需要路由定义的组件 |
(1) 通过URL直接访问的页面; (2) 需要深度链接的页面; (3) 需要浏览器导航功能的页面; | (1)纯UI组件:按钮/卡片; (2)业务子组件:表单/列表; (3)弹窗/抽屉等临时组建 |
举个栗子更好理解:如下图所示,我的 view 页面定义了几个页面组件,admin 管理员界面、OrderCenter 订单中心页面、Login 登陆页面等都是需要在浏览器直接展示的;
如果映射关系对应的 index.js 文件中,代码如下:path 就是浏览器要展示URL路径地址,然后对应 component 中所写的页面组件,这样一来就可以通过此路由文件来控制展示给用户的界面了。此外,404Not Found 页面通常要写在末尾,否则会报错,这一点要注意!
// 1. 导入需要用的组件
import VueRouter from 'vue-router';// 2. 定义路由映射关系
// path:自定义的,就是浏览器上显示的URL路径;
// name:自定义名称,通常可以命名为文件名称;
// component: 编写组件所在的文件路径
const routes = [{path:'/admin/admin1',name:'admin1',component:()=>import('../view/admin/admin1.vue')},{path:'/orders/OrderCenter',name:'OrderCenter',component:()=>import('../view/orders/OrderCenter.vue')},{path:'/user/Login',name:'Login',component:()=>import('../view/user/Login.vue')},{path:'/user/UserCenter',name:'UserCenter',component:()=>import('../view/user/UserCenter.vue')},{path:'/Home',name:'Home',component:()=>import('../view/Home.vue')},{path:'/404',name:'404',component:()=>import('../view/404.vue')},
]// 3. 创建路由实例
const router = new VueRouter({mode:'history',routes
})// 4. 导出当前路由组件,供其它组件使用
export default router;
另外一个小芝士,component 组建的路径定义中,我使用了 ".." ,表示当前文件夹的上一层,就拿 admin.vue 路由举例,有些小伙伴可能见过下面这种写法。
{path:'/admin/admin1',name:'admin1',component:()=>import('@/view/admin/admin1.vue')},
其实在这里 "@" 和 ".." 表述的意思相近,".." 表示当前文件夹的上一层,就是 view 文件的上一层 src,是相对路径。
而"@"是 webpack/Vite 的路径别名,默认指向项目根目录下的 src 目录,与文件位置无关,是绝对路径。
二者的区别如下表格
特性 | .. (相对路径) | @ (绝对路径) |
解析基础 | 基于当前文件位置 | 基于工程根目录 |
是否受文件位置影响 | 是 (文件移动会导致路径失效) | 否 (全局有效) |
推荐度 | ★★☆☆☆ | ★★★★★ |
配置位置 | 无需配置 | 需在 webpack/vite 中配置别名 |
典型路径 | ../view/admin/admin1.vue | @/view/admin/admin1.vue |
小编这里图省事,使用了 ".." ,但小编个人更推荐 "@",只是使用"@"需要注意一点,要提前在
如果使用 Vue2 ,要提前在 vue.config.js 中配置@别名,如下所示
const path = require('path')module.exports = {configureWebpack: {name: name,resolve: {alias: {'@': path.resolve( 'src'),}},}
}
如果使用 Vue3 ,要提前在 vite.config.js 中配置@别名,如下所示
import { defineConfig } from 'vite'
import path from 'path'export default defineConfig({resolve: {alias: {'@': path.resolve(__dirname, './src'),}}
})
1.7 store/index.js——状态管理
作用:集中管理应用状态(如用户信息、全局配置),提供响应式数据和状态变更方法
使用 Pinia(Vue 3 推荐)或 Vuex。
示例代码如下:
// store/index.js (Pinia 示例)
import { defineStore } from 'pinia'export const useCounterStore = defineStore('counter', {state: () => ({ count: 0 }),actions: {increment() {this.count++}}
})
在经过 export 导出之后, 当前组件会被 main.js 文件注册并导入到 Vue 应用中。
其他组件通过 useStore()
(Pinia)或 this.$store
(Vuex)访问状态。
1.8 app.vue
作用:应用顶级组件,所有页面的容器;
app.vue 内部通常定义全局布局(如导航栏/页脚),全局样式等,包含路由视图容器 <router-view>
<template><div id="app"><!-- 全局导航栏 --><nav><router-link to="/">Home</router-link> |<router-link to="/about">About</router-link></nav><!-- 路由页面渲染区 --><router-view/><!-- 全局页脚 --><footer>© 2025 ZhangSir</footer></div>
</template><script>
// 导出当前 app 根容器,然后在 main.js 文件中导入使用
export default {name: 'App',// 可在此添加全局逻辑(如用户登录状态检查)
}
</script><style scoped>
/* 全局样式 */
nav { padding: 20px; }
footer { margin-top: 50px; }
</style>
1.9 main.js 应用初始化脚本
作用:创建Vue应用实例、集成全局插件(路由Router、状态管理Store等)、挂载应用到DOM
如下实例代码,
第一步:导包。将我们上面定义的路由router、状态管理store、Vue根容器以及一些其他第三方组件如ElementUI等全部导入;
第二步:注入。创建一个 Vue 实例,并将导入的包全部注入到 Vue 实例中;
第三步:挂载。将 Vue 实例挂载到 id = "app" DOM节点上(此处的DOM节点通常就是指我们下面 index.html 文件中 id = "app" 的 div 元素块)。
import { createApp } from 'vue'
import App from './App.vue'
import router from './router' // 导入路由配置
import store from './store' // 导入状态仓库const app = createApp(App)
// 注入路由系统
app.use(router)
// 注入状态管理
app.use(store)
// 挂载到id="app"的DOM节点
app.mount('#app')
// 可选:全局错误处理
app.config.errorHandler = (err) => {console.error('全局错误:', err)
}
1.10 HTML 应用入口容器文件
作用:整个单页面应用(SPA)的HTML基础模板、提供Vue应用挂载的根节点(<div id="app">
)、引入全局CSS/JS资源(如字体、SDK等)。
是整个Vue文件的基础模板,通常含有一个根元素,提供上方创建的 vue 应用挂载的根节点(<div id = "app">),Vue 应用会挂在到这个元素上。此外,还可以引入打包后的JavaScript、CSS文件作为全局的CSS、JS资源(如字体、SDK等)。
示例代码如下
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>My Vue App</title><!-- 引入全局样式 --><link rel="stylesheet" href="https://cdn.example.com/font.css"><!-- 引入打包后的JS文件 --><script src="./dist/main.js"></script>
</head>
<body><!-- Vue应用挂载点 --><div id="app"></div><!-- 构建工具会自动注入打包后的JS文件 --><script type="module" src="/src/main.js"></script>
</body>
</html>
二. Vue 项目启动文件加载流程及依赖分析
1. index.html 文件被浏览器加载,其中包含一个挂载点,通常为<div id = “app”>和引入 main.js 的脚本文件;
2. main.js 文件执行:
导入 Vue 库、导入 App.vue 作为根组件、导入 router 和 store、创建 Vue 实例并将 router 和 store 注入到 Vue 中,再渲染 app.vue。
3. app.vue 被渲染
在模板中可能含有全局组件(如<NavBar> 和 <router-view>);此时,router开始根据文件内的URL地址匹配路由;
4. router 根据路由配置,加在对应的 view 页面组件,如 Home.vue;
5. view 组件被渲染
在 view 页面组件中,极大概率会引用 component 中的子组件;
在`created`或`mounted`钩子中,可能通过`api`调用接口,或通过`store`访问/修改全局状态;
6. **store** 中的actions可能调用`api`模块,`api`模块使用`utils`中的请求工具发送请求;
7. **components** 组件在`views`中被使用,它们可能使用`assets`中的资源(如图片)或`utils`中的工具函数;
综合上面的话,大致可以总结为下面这张图
各个模块可能出现的依赖关系如下表格
模块/组件/文件名称 | 依赖模块 | 被依赖模块 |
index.html | 无 | main.js (作为入口文件被加载) |
main.js | app.vue (根组件) router (路由系统) store (状态管理系统) assest (静态资源文件) utils (工具包) | 无 (是项目入口,不被其他文件依赖) |
App.vue | router (使用<router-view>) conponent (全局布局组件) assest (应用级样式) | main.js (作为根组件被加载) |
router(router/index.js) | view/*.vue (路由组成) store (路由守卫使用 Vuex) | main.js (初始化) app.vue (提供路由视图容器) |
store(store/index.js) | api (调用网络请求) utils (数据处理工具,各种工具类方法) | main.js (初始化) view/*.vue (使用状态) component (使用状态) |
view(页面组件) | conponent (使用子组件) api (请求调用) store (使用 Vuex) utils (页面工具,各种方法) assest (静态资源,多为图片图标等) | router (被路由引用) |
component(可复用组件) | assest (组件资源) utils (组件工具) 其它 component (组件之间可以互相嵌套) | view (被页面引用) App.vue (全局组件) |
api(请求函数模块) | utils工具模块(如请求拦截器、数据处理等) | store (Vuex action调用) view (页面组件调用) |
assest(静态资源模块) | 无 | App.vue、view 、component |
utils(工具包) | 无 | api、store、view、component |
依赖循环处理,在有些项目中,会出现 store 依赖 utils,同时 utils 也依赖 store 的情况,
解决方案:
1:依赖注入,在 main.js 文件中初始化工具
// main.js
import utils from '@/utils'
import store from './store'utils.setStore(store) // 注入store依赖
2:延迟加载,在函数内部 require
// utils/auth.js
export const getToken = () => {const store = require('@/store') // 动态引入return store.state.token
}
3:架构重组,将共享功能提取到独立模块