day18 前端Web实战-Vue工程化

实战总结:Vue 工程化

本文是对《15-前端Web实战(Vue工程化+ElementPlus)》讲义中 Vue 工程化部分 的总结,并补充了自行了解的”侧边栏 + 动态组件加载”实现。
这一天是前端开发从”脚本式引入 Vue”迈向”工程化开发”的转折点,目标:掌握 Vue 脚手架项目结构、组合式 API 写法,理解单文件组件开发流程。


上篇:Vue 工程化

一、为什么需要前端工程化

1. 传统开发模式的问题

之前在 HTML 中直接用 <script src="vue.js"> 引入 Vue / Axios,存在三大痛点:

问题 说明
不规范 每次开发从零开始,无统一标准
难复用 多个页面中的组件共用性差
难维护 js、图片等资源无规范化存储目录

2. 工程化的 4 个特点

1
2
3
4
模块化  →  将 js/css 做成可复用模块
组件化 → 将 UI + 样式 + 行为封装成组件
规范化 → 统一目录结构与编码规范
自动化 → 构建/测试/部署自动完成

一句话:前端工程化就是把开发所需的工具、技术、流程、经验进行规范化和标准化,从而统一规范、提效、降难、提复用。


二、环境准备

1. create-vue 脚手架介绍

  • create-vue 是 Vue 官方最新的脚手架工具,用于快速生成工程化 Vue 项目。
  • 提供功能:统一目录结构、本地调试、热部署、单元测试、集成打包上线。
  • 依赖环境:必须先安装 NodeJS

2. NodeJS 安装要点

步骤 关键点
安装目录 没有中文、没有空格(如 D:\develop\NodeJS
验证安装 命令行执行 node -v
配置全局路径 npm config set prefix "D:\develop\NodeJS"用管理员身份运行)
切换淘宝镜像 npm config set registry https://registry.npmmirror.com

⚠️ 重点提醒prefix 后的路径必须是 NodeJS 的安装目录,配置错误会导致全局包安装位置错乱。

3. npm 介绍

  • npm = Node Package Manager,NodeJS 的软件包管理器。
  • 开发中需要的依赖通过 npm install xxx 从远程仓库下载到本地。

三、Vue 项目创建与结构

1. 创建命令

1
npm create vue@3.3.4

执行后的交互选项(默认值基本都是 No,按需开启):

选项 含义 默认
Project name 项目名称 vue-project
Add TypeScript 是否加入 TS No
Add JSX Support 是否加入 JSX 支持 No
Add Vue Router 是否添加路由(单页应用) No
Add Pinia 是否添加状态管理 No
Add Vitest 是否添加单元测试 No
Add E2E testing 是否添加端到端测试 No
Add ESLint 是否添加代码质量检查 No

2. 项目结构

1
2
3
4
5
6
7
8
9
10
vue-project/
├── index.html # 页面入口,包含 #app 挂载点
├── package.json # 项目依赖和脚本
├── vite.config.js # Vite 配置(插件、别名等)
└── src/ # ← 核心开发目录
├── main.js # 应用入口:创建并挂载根组件
├── App.vue # 根组件
├── views/ # 页面级组件
├── components/ # 可复用组件
└── assets/ # 静态资源(CSS、图片)

📌 重点:以后操作最多的就是 src 目录,前端代码都在这里写。

3. 启动项目

方式 命令/操作
命令行 npm run dev
VSCode 图形化 点击 NPM 脚本中 dev 后的运行按钮

启动后访问:http://localhost:5173


四、Vue 项目开发流程(SFC)

1. 什么是 SFC

SFC(Single-File Components,单文件组件):Vue 将一个组件的逻辑 (JS)、模板 (HTML)、样式 (CSS) 封装在同一个 *.vue 文件里。

2. .vue 文件三段式结构

1
2
3
4
5
6
7
8
9
10
11
<script setup>
// 1. 逻辑:导入、响应式数据、函数、钩子
</script>

<template>
<!-- 2. 模板:HTML 结构 + Vue 指令 -->
</template>

<style scoped>
/* 3. 样式:scoped 表示只作用于当前组件 */
</style>

五、API 风格(重点对比)

Vue 组件有两种写法:组合式 API选项式 API

1. 组合式 API(Vue3 推荐)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script setup>
import { ref, onMounted } from 'vue';

const count = ref(0); // 响应式变量

function increment() { // 普通函数
count.value++;
}

onMounted(() => { // 钩子函数
console.log('Vue Mounted....');
})
</script>

<template>
<input type="button" @click="increment"> Count : {{ count }}
</template>

2. 选项式 API(Vue2 风格)

1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
export default {
data() { // 数据
return { count: 0 }
},
methods: { // 方法
increment: function() { this.count++ }
},
mounted() { // 钩子
console.log('vue mounted.....');
}
}
</script>

3. 两种风格对比

对比项 组合式 API 选项式 API
标识 <script setup> <script> + export default
数据定义 const x = ref(值) data() { return {...} }
方法定义 普通函数 function fn() {} 写在 methods 选项里
钩子函数 onMounted(() => {}) mounted() {}
this 指向 没有 this(undefined) 指向当前组件实例
访问响应式值 count.value this.count
推荐度 ✅ Vue3 主推 兼容 Vue2 老项目

⚠️ 易混淆:组合式 API 中没有 this!访问 ref 变量必须用 .value(模板里除外,模板自动解包)。


六、案例:UserList.vue(组合式 API + Axios)

1. 安装 axios

1
npm install axios

2. views/UserList.vue 完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
<script setup>
import { ref, onMounted } from 'vue'
import axios from 'axios'

// 1. 声明响应式数据
const userList = ref([]) // 员工列表
const name = ref('')
const gender = ref('')
const job = ref('')

// 2. 声明查询函数
const search = () => {
axios.get(`https://web-server.itheima.net/emps/list?name=${name.value}&gender=${gender.value}&job=${job.value}`)
.then(res => {
userList.value = res.data.data
})
}

// 3. 钩子:页面加载完毕自动查询
onMounted(() => {
search()
})
</script>

<template>
<div id="center">
姓名: <input type="text" v-model="name">
性别:
<select v-model="gender">
<option value="1"></option>
<option value="2"></option>
</select>
职位:
<select v-model="job">
<option value="1">班主任</option>
<option value="2">讲师</option>
<option value="3">其他</option>
</select>
<input class="btn" type="button" value="查询" @click="search">
</div>

<table>
<tr>
<th>序号</th><th>姓名</th><th>头像</th><th>性别</th>
<th>职位</th><th>入职时间</th><th>更新时间</th>
</tr>
<tr v-for="(user, index) in userList" :key="user.id">
<td>{{ index + 1 }}</td>
<td>{{ user.name }}</td>
<td><img :src="user.image"></td>
<td>
<span v-if="user.gender == 1"></span>
<span v-else-if="user.gender == 2"></span>
<span v-else>其他</span>
</td>
<td>
<span v-show="user.job == 1">班主任</span>
<span v-show="user.job == 2">讲师</span>
<span v-show="user.job != 1 && user.job != 2">其他</span>
</td>
<td>{{ user.entrydate }}</td>
<td>{{ user.updatetime }}</td>
</tr>
</table>
</template>

<style scoped>
table, th, td { border: 1px solid #000; border-collapse: collapse; line-height: 50px; text-align: center; }
#center, table { width: 60%; margin: auto; }
#center { margin-bottom: 20px; }
img { width: 50px; }
input, select { width: 17%; padding: 10px; margin-right: 30px; border: 1px solid #ccc; border-radius: 4px; }
.btn { background-color: #ccc; }
</style>

3. App.vue 引入组件

1
2
3
4
5
6
7
<script setup>
import UserList from './views/UserList.vue'
</script>

<template>
<UserList></UserList>
</template>

4. 数据驱动流程

1
2
3
4
5
6
7
声明数据 (ref)

onMounted 自动调用 search()

axios 异步请求 → 赋值 userList.value

Vue 响应式更新 → v-for 重新渲染表格

扩展篇:侧边栏 + 动态组件加载(自行了解)

这部分是讲义之外,基于”侧边栏菜单切换不同页面”需求自己研究的实现方式。

一、需求场景

很多后台管理系统都有这种布局:

1
2
3
4
5
6
┌──────────┬──────────────────────┐
│ 侧边栏 │ 内容区 │
│ ─主页 │ │
│ ─热门 │ (点击菜单切换内容) │
│ ─我的 │ │
└──────────┴──────────────────────┘

核心难点:点击左侧菜单,右侧内容区切换成不同组件,但整个页面不刷新。


二、动态组件 <component :is="...">

1. 是什么

<component> 是 Vue 内置的特殊组件,不是 HTML 原生标签。它通过 :is 属性决定渲染哪个组件。

2. 基本写法

1
<component :is="当前要显示的组件" />
  • :is 后面跟的是组件对象(不是字符串)。
  • :is 的值变化时,右侧渲染的组件自动切换。

3. 配合菜单数据使用

1
2
3
4
5
6
7
8
9
10
11
12
13
import { ref } from 'vue'
import HomeView from './views/HomeView.vue'
import HotView from './views/HotView.vue'
import MineView from './views/MineView.vue'

// 菜单数据:name 是显示文字,component 是组件对象
const menus = [
{ name: '主页', component: HomeView },
{ name: '热门', component: HotView },
{ name: '我的', component: MineView },
]

const activeIndex = ref(0) // 当前选中菜单的索引
1
2
<!-- 右侧动态渲染选中菜单对应的组件 -->
<component :is="menus[activeIndex].component" />

📌 component 是自定义字段名,可改成 compview 等,但模板中要保持一致。


三、侧边栏布局实现

1. App.vue 完整结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<script setup>
import { ref } from 'vue'
import HomeView from './views/HomeView.vue'
import HotView from './views/HotView.vue'
import MineView from './views/MineView.vue'

const menus = [
{ name: '主页', component: HomeView },
{ name: '热门', component: HotView },
{ name: '我的', component: MineView },
]

const activeIndex = ref(0)
</script>

<template>
<div class="layout">
<!-- 侧边栏 -->
<aside class="sidebar">
<ul>
<li
v-for="(item, index) in menus"
:key="item.name"
:class="{ active: index === activeIndex }"
@click="activeIndex = index"
>
{{ item.name }}
</li>
</ul>
</aside>

<!-- 内容区:动态组件 -->
<main class="content">
<component :is="menus[activeIndex].component" />
</main>
</div>
</template>

<style scoped>
.layout {
display: flex; /* 侧边栏 + 内容区水平排列 */
}

.sidebar {
width: 180px; /* 侧边栏固定宽度 */
background: #f5f5f5;
}

.sidebar li {
padding: 15px;
cursor: pointer;
list-style: none;
}

.sidebar li.active {
background: #409eff; /* 选中项高亮 */
color: #fff;
}

.content {
flex: 1; /* 占据剩余空间 */
padding: 20px;
}
</style>

2. 关键语法点拆解

语法 作用 本例
v-for="(item, index) in menus" 循环渲染菜单 生成每个 <li>
:key="item.name" 列表项唯一标识(优化性能) 用 name 作 key
:class="{ active: index === activeIndex }" 动态绑定 class 选中项加 active 类
@click="activeIndex = index" 点击修改索引 切换高亮 + 切换组件
<component :is="..."> 动态组件 渲染对应页面

四、数据驱动流程图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
数据定义
menus (菜单数组) + activeIndex (选中索引, ref(0))

模板渲染
v-for 生成菜单 <li>
:class 根据 activeIndex 高亮当前项
<component :is> 渲染 menus[0].component

用户点击菜单
@click="activeIndex = index"

Vue 响应式触发
activeIndex 变化 → :class 重新计算 → 高亮切换
→ :is 重新计算 → 组件切换

视图更新
右侧内容区换成新组件

五、进阶:keep-alive 缓存组件状态

问题:默认情况下,<component :is> 切换组件时,旧组件会被销毁,新组件重新创建。如果组件内有输入数据(如表单),切换回来会丢失。

解决:用 <keep-alive> 包裹动态组件,缓存不活动的组件实例。

1
2
3
4
5
<main class="content">
<keep-alive>
<component :is="menus[activeIndex].component" />
</keep-alive>
</main>
对比 不用 keep-alive 用 keep-alive
切换时 销毁旧组件 → 创建新组件 缓存旧组件 → 复用
状态保留 ❌ 丢失(如表单输入) ✅ 保留
适用场景 切换频繁、状态不重要 需保留状态(如表单、滚动位置)

六、如何新增菜单项

1
2
3
4
5
1. 在 src/views/ 下新建 NewView.vue
2. 在 App.vue 中 import NewView from './views/NewView.vue'
3. 在 menus 数组中追加:
{ name: '新菜单', component: NewView }
4. 无需改其他代码,菜单自动出现

常见问题 & 易混淆点

问题 答案
import 何时用 {} 具名导出{}(如 import { ref } from 'vue');默认导出不用(如 import App from './App.vue'
.vue 文件是默认还是具名导出? 默认导出
<component> 是 HTML 原生标签吗? ❌ 不是,是 Vue 内置特殊组件
:is 后面跟字符串还是组件对象? 组件对象(导入的变量)
组合式 API 里有 this 吗? ❌ 没有,this 是 undefined
ref 变量在 JS 中怎么访问值? .value(如 count.value);模板中自动解包
scoped 的作用? 样式只作用于当前组件,不污染全局
侧边栏宽度在哪改? .sidebarwidth 属性
内容区如何占满剩余宽度? flex: 1
动态组件切换后状态丢失? <keep-alive> 包裹
v-ifv-show 区别? v-if 销毁/创建 DOM;v-show 只切换 display

常用命令速查

1
2
3
4
5
6
7
8
9
10
11
12
13
# 环境配置
npm config set prefix "NodeJS安装目录" # 配置全局安装路径
npm config set registry https://registry.npmmirror.com # 淘宝镜像

# 项目创建与运行
npm create vue@3.3.4 # 创建 Vue 项目
npm install # 安装项目依赖
npm run dev # 启动开发服务器
npm run build # 构建生产版本
npm run preview # 预览构建结果

# 依赖安装
npm install axios # 安装 axios

Vue 核心模板语法速查表

语法 作用 示例
{{ 表达式 }} 文本插值 {{ user.name }}
v-model 双向数据绑定 <input v-model="name">
v-for 列表循环 v-for="(item, index) in menus"
:key 列表项唯一标识 :key="item.id"
:class 动态绑定类名 :class="{ active: isActive }"
:src 动态绑定属性 <img :src="user.image">
@click 监听点击 @click="activeIndex = index"
v-if / v-else-if / v-else 条件渲染(销毁/创建) v-if="user.gender == 1"
v-show 条件显示(display 切换) v-show="user.job == 1"
<component :is> 动态组件 <component :is="menus[i].component" />

📌 复习建议

  1. 对照本文,理解 Vue 工程化项目每个文件的作用(main.js / App.vue / views/)。
  2. 组合式 API 是后续开发主力,重点掌握 refonMounted<script setup> 写法。
  3. 侧边栏 + 动态组件是后台管理系统的经典布局,建议手敲一遍加深理解。
  4. 动手试试:新增一个菜单项、改侧边栏宽度、用 keep-alive 缓存组件状态。