Vue项目
# Vue项目
# 介绍
Vue项目是一套完整的前端工程化体系,适合中大型、需要可维护、可扩展的应用。虽然Vue也可以直接在HTML中使用,但只适合一次性页面或学习阶段的快速实验。
一个完整的Vue项目通常包括:
- 项目脚手架:负责初始化目录、配置构建工具(如Vite、Vue‑CLI)。
- 组件系统:把页面拆分成若干个单文件组件,支持复用与封装。
- 响应式API:让数据变化自动映射到视图。
- 路由和状态管理:帮助组织多页面应用和跨组件共享状态。
- 生态插件:满足从单页小工具到大型系统的各种需求。
- 构建与部署:通过Rollup/Vite打包、代码分割、压缩、生成静态资源,配合CI/CD实现持续交付。
# Vue‑CLI
# 介绍使用
Vue CLI是Vue.js官方提供的脚手架工具,用于快速搭建和配置Vue项目,支持工程化开发。
需要先安装Node.js环境,目前Vue-CLI最高支持Node 22版本。
# 安装脚手架
yarn global add @vue/cli
# 创建Vue项目,执行后会弹出交互式界面供我们进行配置
vue create [项目名]
# 选择预设,我们选手动选择
? Please pick a preset:
Default ([Vue 3] babel, eslint)
Default ([Vue 2] babel, eslint)
> Manually select features
# 选择要安装的功能,空格选择Babel、Router、Vuex即可,然后回车确认
? Check the features needed for your project: (Press <space> to select, <a> to toggle all, <i> to invert selection, and <enter> to proceed)
>(*) Babel # 向后兼容工具包,能将新JS语法转为旧JS语法以兼容旧浏览器
( ) TypeScript
( ) Progressive Web App (PWA) Support
(*) Router # 路由管理包
(*) Vuex # 状态管理包
( ) CSS Pre-processors
( ) Linter / Formatter # 格式检查工具,会严格检查JS语法,如行末的分号,所以不安装
( ) Unit Testing
( ) E2E Testing
# 选择Vue版本
? Choose a version of Vue.js that you want to start the project with (Use arrow keys)
> 3.x
2.x
# 选Y使用历史模式进行路由管理
# 不启用则默认使用哈希模式,URL中会使用#,浏览器只在前端解析它,不会把#及其后面的部分发送给服务器
# 启用历史模式则URL中不会使用#,而是和普通URL一致,浏览器会把完整的路径发送给服务器
Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n)
# 选择第一个将Babel配置文件放在专用文件
Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys)
> In dedicated config files
In package.json
# 是否将配置保存为预设,保存的话,下次再创建项目时可以直接使用该预设进行搭建
Save this as a preset for future projects? (y/N)
# 设置预设的名称
Save preset as: Vue3
# 选择模块包管理工具
? Pick the package manager to use when installing dependencies: (Use arrow keys)
> Use Yarn
Use NPM
# 然后等待项目搭建完毕即可
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
# 项目结构
根目录
| 文件/目录 | 主要作用 |
|---|---|
| node_modules/ | 存放安装的所有依赖包,构建、运行时会直接引用这里的代码。 |
| public/ | 存放不会被WebPack处理的静态资源,例如index.html(唯一入口页面)、favicon.ico(图标)等。 |
| src/ | 项目源码的核心目录,所有Vue组件、路由、状态管理、业务代码均放在这里。 |
| package.json | 项目元信息、依赖声明、脚本命令。 |
| README.md | 项目说明文档,通常包含项目简介、启动方式、目录结构概览等。 |
| .gitignore | 指定 Git 在提交时忽略的文件或目录,如 node_modules、dist。 |
| babel.config.js | Babel 编译配置,负责把ES6+代码转为兼容性更好的ES5。 |
| vue.config.js | Vue‑CLI的可选配置文件,用于自定义WebPack、代理、路径别名等。 |
| jsconfig.json | 用于为编辑器提供JavaScript项目的类型信息、路径别名以及编译选项。 |
./public/index.html:单页面应用的唯一入口页面,其中的<div id="app"></div>是Vue的挂载点。
src目录
| 文件/目录 | 主要作用 |
|---|---|
| assets/ | 存放会被WebPack处理的静态资源,例如图片、字体、全局样式等。 |
| components/ | 可复用的小组件,例如工具组件、UI组件等。 |
| views/ | 路由对应的页面级组件。 |
| router/ | 路由配置,定义路径和组件的映射。 |
| utils/ | 项目通用的工具函数。 |
| api/ | 与后端交互的封装(axios实例、接口函数)。 |
| store/ | Vuex状态管理,模块化存放 |
| App.vue | 根组件,包含全局布局、<router-view/>占位。 |
| main.js | 入口文件,创建 Vue 实例、挂载根组件、引入路由/状态管理。 |
# 运行项目
# 运行命令在生成的package.json文件的scripts字段中可以查看到
# 然后直接在项目目录用命令行执行即可
# 开启服务端并监听HTTP请求,适用于调试阶段
yarn serve
# 将Vue项目打包成静态资源文件,会保存到项目根目录的dist目录中
yarn build
2
3
4
5
6
7
8
# Vite
# 介绍及特点
Vite是由Vue官方主导开发的新一代前端构建工具。相较于Vue CLI来说它的构建打包、服务器启动、热更新的速度更加快,所以推荐新项目首选Vite。
他有如下几个特点:
极速冷启动:启动本地HTTP服务器时无需打包,直接利用浏览器原生ES模块加载源码。
按需编译:使用本地HTTP服务器时,只编译当前页面用到的文件。
支持多框架:不仅支持Vue,还支持React、Svelte、Lit等。
现代化:基于 ESBuild(Go编写,构建速度快) + Rollup(生产打包文件更小)。
# 创建项目
# 安装并创建项目
yarn create vite my-app --template vue
# 是否使用实验性构建底层工具,选择No
Use rolldown-vite (Experimental)?:
> No
# 是否使用yarn安装并立即启动,选择YES
Install with yarn and start now?
> Yes
2
3
4
5
6
7
8
9
10
11
12
# 插件安装
创建的项目默认不带任何插件,需要自行安装挂载存储、路由等插件。
# 安装存储、路由、HTTP请求插件
yarn add pinia vue-router axios
# 安装自动导入组件、自动导入API插件,使用后无需手动ref, computed等接口
yarn add -D unplugin-auto-import unplugin-vue-components
2
3
4
5
# 配置文件
vite的配置在项目目录下的 vite.config.js 文件中。
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import {fileURLToPath, URL} from 'url'
export default defineConfig({
// 部署时的公共基础路径,会在所有资源路径前加上
base: '/',
// 定义使用的插件列表
plugins: [
// 启用Vue单文件组件支持
vue(),
// 自动导入Vue相关API
AutoImport({
// 要自动导入的库
imports: ['vue', 'vue-router'],
// 自动扫描并导入目录下的函数(必须以use开头)
dirs: ['src/stores'],
// 不生成类型声明文件(因为不用TS)
dts: false,
// 在<template>中也能使用(比如@click="increment")
vueTemplate: true,
}),
// 自动导入并注册组件
Components({
// 组件目录(默认就是src/components)
dirs: ['src/components'],
// 不生成类型声明
dts: false,
// 允许子目录组件(如components/layout/Header.vue → 可用为LayoutHeader)
deep: true,
// 组件名格式,保持原名(不加前缀)
directoryAsNamespace: false
}),
],
// 模块解析配置
resolve: {
// 路径别名,让@指向src目录,使路径支持@/写法,避免../../写法
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
// 开发服务器配置(在vite dev时生效)
server: {
// 监听端口
port: 3000,
// 启动时是否自动打开浏览器
open: true,
// 是否严格使用指定端口(被占用时退出而非换端口)
strictPort: false,
// 允许局域网访问
host: true,
// 代理配置(可用于解决开发时跨域问题)
proxy: {
// 将/api开头的请求代理到后端服务
'/api': {
// 目标后端地址
target: 'http://localhost:8080',
// 是否更改 origin 头(一般设为 true)
changeOrigin: true,
// 重写路径去掉/api前缀再转发,例如/api/user → 转发为/user
rewrite: (path) => path.replace(/^\/api/, ''),
},
}
},
// 构建配置(在vite build时生效)
build: {
// 输出目录
outDir: 'dist',
// 是否生成source map,用于调试生产环境问题,建议开发时使用
sourcemap: true,
// 压缩工具:esbuild(速度更快) 或 terser(压缩率更高)
minify: 'terser',
// Terser压缩选项(仅在使用terser时生效)
terserOptions: {
compress: {
// 是否删除console.log、debugger等,生产环境推荐
drop_console: true,
drop_debugger: true,
},
},
rollupOptions: {
output: {
// 将第三方依赖从应用代码文件中拆分出来,打包成独立的JS文件,让依赖代码文件可以长期缓存,不用每次修改业务代码都要重新下载一遍依赖文件
manualChunks(id) {
if (id.includes('node_modules')) {
return 'vendor';
}
},
}
}
}
})
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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# 使用路由
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '@/views/HomeView.vue'
import AboutView from '@/views/AboutView.vue'
const routes = [
{
path: '/',
name: 'home',
component: HomeView,
},
{
path: '/about',
name: 'about',
component: AboutView,
},
]
const router = createRouter({
history: createWebHistory(),
routes,
})
export default router
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// src/main.js
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router'
createApp(App).use(router).mount('#app')
2
3
4
5
6
7
8
# 环境变量
vite支持在 .env 文件中定义环境变量,且可以在代码中使用,但只有以 VITE_ 为前缀的变量才会暴露出来供代码使用。
# .env文件中定义
VITE_API_BASE_URL=https://api.example.com
# JS代码中使用
console.log(import.meta.env.VITE_API_BASE_URL)
2
3
4
5
# 运行项目
# 开启服务端并监听HTTP请求,适用于调试阶段
yarn dev
# 将Vue项目打包成静态资源文件,会保存到项目根目录的dist目录中
yarn build
# 预览生产版本
yarn preview
2
3
4
5
6
7
8
# 父子组件通信
# 介绍
组件之间的数据是独立的,需要通信来传递数据。父传子用Props,子传父用Events。
父组件用在子组件引用标签内用
v-bind传递数据,子组件用props接收数据。子组件用Vue专属的方法
this.$emit发送自定义事件来传递数据,父组件通过事件处理函数接收数据。
# 例子
ParentView.vue 父组件文件(选项式)
<template>
<div id="parent">
<h1>{{ say }}</h1>
<!--
:parent_name='name' 通过属性传递数据给子组件
@child-btn="handleChildBtn" 定义并绑定自定义事件,用于接收处理子组件发来的数据
-->
<Child :parent_name='name' @child-btn-click="handleChildBtn"></Child>
</div>
</template>
<script>
// 导入子组件
import Child from '@/components/Child';
export default {
name: "ParentView",
data() {
return {
say: "I'm Parent",
name: "Evil",
};
},
methods: {
handleChildBtn(data) {
console.log("收到子组件发来的数据 => ", data)
}
}
,
// 注册组件
components: {
Child
}
}
</script>
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
Child.vue 子组件文件(选项式)
<template>
<div id="child">
<!-- 使用父组件传递过来的数据 -->
<p>My parent is => {{ parent_name }}</p>
<!-- 触发函数发送自定义事件 -->
<button @click="sendData">点我</button>
</div>
</template>
<script>
export default {
name: "Child",
// 使用props接收父组件传递过来的参数,接收后可直接使用
props: ["parent_name"],
methods: {
sendData() {
// 触发Vue自定义事件发送数据
this.$emit("child-btn-click", ["你好", "我是子组件的数据"])
}
}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
组合式需要使用
defineProps(['参数名',...])来接收。
# props参数
我们还可以在子组件的props中指定参数的默认值、是否必传、类型等。
<script>
export default {
name: "Child",
props: {
name: {
// 指定name参数是否必传
required: true,
// 指定参数类型,如果传入值不符合该类型会报错
type: String
},
message: {
// 父组件没有传该参数时message的默认值
default: "hello"
}
}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# ref属性模板
# 介绍
ref属性用于直接引用DOM元素或组件实例,在模板中通过 ref 属性给元素或子组件打上标记,然后在JS代码中就能直接对元素或子组件进行引用。
// 标签添加ref标记
<div ref="myDiv">I am tag</div>
<Child ref="myChild">I am component</Child>
2
3
# 选项式使用
选项式API需要通过
this.$refs.[ref属性值]对象来引用,它包含所有带有ref属性的元素或组件,在mounted钩子之后才能访问到$refs。
<template>
<div id="parent">
<!-- 给元素添加ref属性 -->
<div ref="myDiv">{{ say }}</div>
<!-- 给子组件实例添加ref属性 -->
<Child ref="myChild"></Child>
<button @click="handleBtn">点我</button>
</div>
</template>
<script>
// 导入组件
import Child from '@/components/Child';
export default {
name: "ParentView",
data() {
return {
say: "I'm Parent",
};
},
methods: {
handleBtn() {
// 获取并修改元素的文本内容
this.$refs.myDiv.textContent = "Who are you?"
// 获取并修改子组件中的响应式属性
this.$refs.myChild.name = "you are child"
}
}
,
// 注册组件
components: {
Child
}
}
</script>
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
# 组合式使用
组合式API中ref属性需要绑定到名称相同的响应式对象变量来引用,然后通过
[变量名].value来访问。
<template>
<div id="parent">
<!-- 将元素绑定到ref对象 -->
<div ref="myDiv">{{ say }}</div>
<!-- 将子组件实例绑定到ref对象 -->
<Child ref="myChild"/>
<button @click="handleBtn">点我</button>
</div>
</template>
<script>
import {ref} from 'vue'
import Child from '@/components/Child'
export default {
name: 'ParentView',
components: {
Child
},
setup() {
// 响应式数据
const say = ref("I'm Parent")
// 用于引用元素或子组件实例
const myDiv = ref(null)
const myChild = ref(null)
const handleBtn = () => {
// 修改元素的文本内容
myDiv.value.textContent = 'Who are you?'
// 修改子组件中的响应式属性
myChild.value.name = 'you are child'
}
// 返回模板中需要使用的变量和函数
return {
say, myDiv, myChild, handleBtn
}
}
}
</script>
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
# parent
子组件可以通过 this.$parent.[名称] 来访问父组件的变量或对象。
<template>
<div id="child">
<p>I am {{ name }}</p>
<p>{{ test() }}</p>
</div>
</template>
<script>
export default {
name: "Child",
data() {
return {name: "John"}
},
methods:{
test(){
return this.$parent.say
}
}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# ref函数
# 介绍
ref(原始值) 用于创建响应式数据,它接收一个原始值,并将其作为内部值返回一个ref对象 (响应式引用对象),该对象的内部有一个 .value 属性,用于访问或修改内部值。
Vue3的响应式系统基于Proxy,但Proxy只能代理对象,所以就需要ref函数来将原始值包装成对象。
# 组合式例子
选项式会自动将data选项返回的数据包装成响应式,无需使用ref()函数。
<template>
<div class="hello">
{{ msg }}
<input type="text" v-model="msg" />
<button @click="handleBtn">点我</button>
</div>
</template>
<script>
import {ref} from 'vue'
export default {
name: "Hello",
setup() {
// 定义响应式对象变量
let msg = ref("I am msg!")
// 修改响应式对象的值,响应式数据更新后会实时同步到页面
const handleBtn = () => {
msg.value = "I am msg again!"
}
return { msg, handleBtn }
}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
在模板中使用响应式数据时,不需要写
.value,Vue会自动解包。
# reactive函数
reactive() 和 ref() 函数作用类似,不同的是 reactive() 用于将一个普通对象转换为响应式对象。
# 组合式例子
<script setup>
import { reactive } from 'vue'
const state = reactive({
count: 0,
name: 'Alice',
items: []
})
// 解构需要使用toRefs才能保持响应性
const { count } = toRefs(state)
</script>
2
3
4
5
6
7
8
9
10
11
12
# pinia状态管理器
# 介绍
Pinia是Vue官方推荐的状态管理库,在Vue中,状态就是组件里保存的数据,而状态管理器就是一个全局的数据中心,所有组件都可以直接读写这个中心的数据,无需层层传递。
如果不使用状态管理器,多个组件想要共享数据则只能通过如下方式:
- 通过props一层层传(祖传 props)
- 用事件 $emit 一层层冒泡(回调地狱)
- 用 mitt / eventBus 全局发消息(难以追踪)
会导致代码混乱、难以维护、容易出错等问题。
# 使用方式
- 安装Pinia
npm install pinia
- 在main.js中启用Pinia
// 导入方法
import { createPinia } from 'pinia'
// 创建pinia实例
const pinia = createPinia()
// 将pinia插件挂载到Vue应用
app.use(pinia)
2
3
4
5
6
7
8
- 创建一个Store(仓库)
例如:src/stores/user.js
import { defineStore } from 'pinia'
// 定义一个叫user的store
// defineStore会返回一个函数,用于返回同一个store实例
export const useUserStore = defineStore('user', {
// state必须是一个函数,用于返回初始数据,相当于组件里的data,会自动将返回的数据转为响应式数据
state: () => ({
name: '',
avatar: '',
isLoggedIn: false
}),
// actions用于修改state
actions: {
login(name, avatar) {
this.name = name
this.avatar = avatar
this.isLoggedIn = true
},
logout() {
this.name = ''
this.avatar = ''
this.isLoggedIn = false
}
}
})
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
- 在组件中修改Store中的状态
例如:view/LoginView.vue
<template>
<button @click="handleLogin">模拟登录</button>
</template>
<script setup>
// 导入定义的store
import { useUserStore } from '@/stores/user'
// 获取store实例
const userStore = useUserStore()
function handleLogin() {
// 调用action修改store中的状态
userStore.login('小明', 'avatar.jpg')
// Pinia也支持直接修改属性
userStore.name = "小张"
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- 在组件中读取Store中的状态
例如:components/Navbar.vue
<template>
<!-- 直接通过"实例.数据名"即可访问 -->
<div v-if="userStore.isLoggedIn">
欢迎,{{ userStore.name }}!
<img :src="userStore.avatar" />
</div>
<div v-else>请登录</div>
</template>
<script setup>
import { useUserStore } from '@/stores/user'
// 获取store实例
const userStore = useUserStore()
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 解构数据
解构state会丢失响应性,结构出来的数据会变成普通值。
import { storeToRefs } from 'pinia'
const { count } = storeToRefs(useCounterStore())
2