Vue3 项目的基本架构解读

目录

[一. Vue项目构成及核心文件解读](#一. Vue项目构成及核心文件解读)

[1.1 assest 静态资源文件](#1.1 assest 静态资源文件)

[1.2 utils 工具包](#1.2 utils 工具包)

[1.3 api,controller,http 包](#1.3 api,controller,http 包)

[1.4 view,page 包的页面组件](#1.4 view,page 包的页面组件)

[1.5 component 公共组件、UI组件](#1.5 component 公共组件、UI组件)

[1.6 router/index.js------路由配置](#1.6 router/index.js——路由配置)

[1.7 store/index.js------状态管理](#1.7 store/index.js——状态管理)

[1.8 app.vue](#1.8 app.vue)

[1.9 main.js 应用初始化脚本](#1.9 main.js 应用初始化脚本)

[1.10 HTML 应用入口容器文件](#1.10 HTML 应用入口容器文件)

[二. Vue 项目启动文件加载流程及依赖分析](#二. 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函数,在项目多处都可以导入使用,极大程度上做到了我们开发中常说的低耦合!

javascript 复制代码
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 是已经提前封装在其他配置文件中了,所以显得简洁一些,如果没有封装,就会变得非常杂乱。

javascript 复制代码
$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 的痕迹,全是纯代码逻辑编写,代码之间的耦合度也降低了,非常的优雅!

javascript 复制代码
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 文件作为一个组件使用导入到其他文件中或被其组件嵌套使用。

javascript 复制代码
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 页面通常要写在末尾,否则会报错,这一点要注意!

javascript 复制代码
// 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 路由举例,有些小伙伴可能见过下面这种写法。

javascript 复制代码
    {
        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 中配置@别名,如下所示

javascript 复制代码
const path = require('path')

module.exports = {
  configureWebpack: {
    name: name,
    resolve: {
      alias: {
        '@': path.resolve( 'src'),
      }
    },
  }
}

如果使用 Vue3 ,要提前在 vite.config.js 中配置@别名,如下所示

javascript 复制代码
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。

示例代码如下:

javascript 复制代码
// 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>

javascript 复制代码
<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 元素块)。

javascript 复制代码
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等)。

示例代码如下

html 复制代码
<!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。

  1. app.vue 被渲染

在模板中可能含有全局组件(如<NavBar> 和 <router-view>);此时,router开始根据文件内的URL地址匹配路由;

  1. router 根据路由配置,加在对应的 view 页面组件,如 Home.vue;

  2. view 组件被渲染

在 view 页面组件中,极大概率会引用 component 中的子组件;

在`created`或`mounted`钩子中,可能通过`api`调用接口,或通过`store`访问/修改全局状态;

  1. **store** 中的actions可能调用`api`模块,`api`模块使用`utils`中的请求工具发送请求;

  2. **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 文件中初始化工具

javascript 复制代码
// main.js
import utils from '@/utils'
import store from './store'

utils.setStore(store) // 注入store依赖

2:延迟加载,在函数内部 require

javascript 复制代码
// utils/auth.js
export const getToken = () => {
  const store = require('@/store') // 动态引入
  return store.state.token
}

3:架构重组,将共享功能提取到独立模块

相关推荐
前端小白从0开始15 分钟前
Vue3项目实现WPS文件预览和内容回填功能
前端·javascript·vue.js·html5·wps·文档回填·文档在线预览
JohnYan33 分钟前
Bun技术评估 - 03 HTTP Server
javascript·后端·bun
开开心心就好1 小时前
高效Excel合并拆分软件
开发语言·javascript·c#·ocr·排序算法·excel·最小二乘法
難釋懷1 小时前
Vue解决开发环境 Ajax 跨域问题
前端·vue.js·ajax
特立独行的猫a1 小时前
Nuxt.js 中的路由配置详解
开发语言·前端·javascript·路由·nuxt·nuxtjs
中微子1 小时前
小白也能懂:JavaScript 原型链和隐藏类的奇妙世界
javascript
咸虾米1 小时前
在uniCloud云对象中定义dbJQL的便捷方法
前端·javascript
梨子同志1 小时前
JavaScript Proxy 和 Reflect
前端·javascript
海的诗篇_1 小时前
移除元素-JavaScript【算法学习day.04】
javascript·学习·算法
汤圆炒橘子1 小时前
状态策略模式的优势分析
前端