Vue 概念

Vue.js 是一款用于构建用户界面的渐进式JavaScript框架它允许开发者创建高效的可复用的以及模块化的前端组件是目前最流行的前端框架之一Vue的核心库专注于视图层易于学习且集成到现有项目中同时也能为复杂的单页应用程序SPA提供强大的功能支持

Vue.js适合用来构建交互式的Web界面无论是简单的嵌入式小部件还是复杂的大型单页应用程序Vue都能提供相应的解决方案由于其灵活性和易用性Vue在全球范围内得到了广泛的应用和认可

主要特点


渐进式框架Vue 是一个渐进式的框架意味着你可以根据项目的需要逐步采用它的不同部分你可以在一个已有的项目中仅使用 Vue 的视图层也可以构建复杂的单页应用程序SPA
双向数据绑定Vue 实现了声明式的双向数据绑定机制可以自动追踪组件依赖的数据在数据变化时自动更新DOM简化了用户输入处理和表单验证等操作
组件化Vue 强调UI界面的组件化开发使得代码更加模块化可重用每个组件都有自己的视图和数据逻辑易于维护和测试
轻量级Vue本身体积较小性能高效非常适合移动设备的应用开发
指令系统Vue提供了诸如v-if, v-for, v-bind, v-model等丰富的指令来操作DOM让开发变得更加简单直观
虚拟DOM使用虚拟 DOM 技术来提高性能通过对比前后两个虚拟 DOM 树的差异只对实际需要更新的部分进行最小化的DOM操作提升应用的响应速度
工具链支持Vue拥有完整的工具链支持如Vue CLI命令行工具Vuex状态管理模式Vue Router路由管理帮助开发者更好地进行项目开发

主要优势


易学易用Vue 的API设计简洁明了文档详尽对于初学者来说非常友好容易上手
灵活性Vue 不强制要求特定的项目结构或工具链可以根据项目的具体需求灵活选择合适的架构和技术栈
强大的生态系统Vue 拥有一个丰富的插件和工具生态系统包括 Vue Router路由管理Vuex状态管理Vue CLI脚手架工具帮助开发者更高效地构建复杂应用 社区支持随着 Vue 的流行其社区也在迅速增长提供了大量的学习资源教程和支持
良好的兼容性Vue 可以很好地与其他库或现有项目集成适应不同的项目需求

应用场景


单页应用程序SPAVue.js 是构建单页应用程序的理想选择通过 Vue Router 可以轻松实现页面的导航和路由管理使得用户在不同视图间切换时无需重新加载整个页面提供更流畅的用户体验
渐进式Web应用PWAue.js 能够很好地支持 PWA 开发借助于服务工作者和缓存API等技术可以创建出具有离线访问能力快速加载速度以及类似原生应用体验的应用程序
动态内容更新Vue.js 提供了高效的双向数据绑定机制能够实时响应数据的变化并自动更新UI这使得它非常适合用于需要频繁更新内容的应用场景如社交网络平台实时聊天应用等
移动应用开发通过与框架如 Ionic 或 Quasar 结合Vue.js 可以用来开发跨平台的移动应用这些框架利用 Vue.js 的组件化模型来构建界面并生成可以在多个平台上运行的代码
桌面应用开发Vue.js 还可用于开发桌面应用程序例如使用 Electron 框架开发者可以使用熟悉的前端技术栈来创建同时适用于 WindowsMac 和 Linux 的桌面应用
企业级应用在企业环境中Vue.js 常被用来构建大型复杂的应用系统结合 Vuex 进行状态管理可以帮助团队更好地组织和管理代码提高开发效率和维护性
电商网站与管理系统Vue.js 可用于构建电商网站后台管理系统等通过组件化开发模式加快开发速度同时也便于后续的功能扩展和维护
插件和库的开发Vue.js 社区非常活跃很多开发者使用 Vue 来创建可复用的插件或库为其他开发者提供便捷的功能和服务

Vue 基础

创建实例

创建 Vue 实例是使用 Vue 的第一步每一个 Vue 应用都是从一个 Vue 实例 开始的你可以把它看作是一个 Vue 应用的它控制着页面中的一部分 DOM 元素并管理其内部的数据方法和生命周期

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<div id="app">
{{ message }}
</div>

<script>
new Vue({
el: '#app',
data: {
message: 'Hello, Vue!'
}
})
</script>
</body>
</html>

<!-- 页面上显示Hello, Vue! -->

插值表达式

插值表达式是最基础最常见的数据绑定方式之一它主要用于将数据动态地插入到 HTML 模板中实现响应式更新Vue 的插值表达式使用双大括号 {{ }} 语法

基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<div id="app">
<h1>{{ title }}</h1>
<p>欢迎你{{ user.name }}</p>
<p>当前时间{{ new Date() }}</p>
</div>

<script>
new Vue({
el: '#app',
data: {
title: 'Vue 插值表达式示例',
user: {
name: '张三'
}
}
})
</script>

<!--
Vue 插值表达式示例
欢迎你张三
当前时间Wed May 29 2025 16:14:00 GMT+0800 (中国标准时间)
-->

主要特性


✅响应式更新当数据变化时插值内容会自动更新
✅支持任意 JS 表达式可以写简单的表达式三目运算符等等...
⚠️仅限文本内容插值表达式不能渲染 HTML 内容防止 XSS如果需要渲染 HTML请使用 v-html 指令
⚠️不支持控制结构如 iffor 等复杂逻辑应在模板外处理或使用计算属性

注意事项

1
2
3
4
5
6
7
8
9
10
11
1. 避免复杂的逻辑
虽然可以在插值中使用表达式但建议不要嵌入过于复杂的逻辑推荐使用 计算属性computed 来代替
computed: {
formattedTime() {
return formatDate(this.currentTime); // 自定义格式化函数
}
}
<p>格式化后的时间{{ formattedTime }}</p>

2. 不支持 HTML 渲染输出的是纯字符串不会渲染为加粗效果
<p>{{ "<strong>加粗文字</strong>" }}</p>

响应式特性

响应式特性是 VUE 核心功能之一它使得数据模型与视图之间能够自动同步这意味着当数据发生变化时Vue 会自动更新相关的视图部分反之亦然这种机制大大简化了前端开发中的状态管理问题

响应式基础

在 Vue.js 中当你创建一个 Vue 实例并定义 data 选项时Vue 会将 data 对象的所有属性转换为响应式属性这意味着当这些属性被读取时Vue 能够追踪到依赖关系当这些属性被修改时Vue 可以通知所有依赖该属性的地方进行更新

1
2
3
4
5
6
7
8
// 在这个例子中message 是一个响应式的属性
// 任何对 message 的修改都会导致相关视图的自动更新
new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
});

如何实现响应式

Vue 3 中采用了 Proxy 对象来实现响应式系统取代了 Vue 2 中使用的 Object.defineProperty 方法这使得 Vue 3 在处理响应式数据方面更加灵活和强大

Proxy通过代理对象拦截对目标对象的操作如读取写入等从而实现在不改变原有对象的基础上对其进行扩展或限制操作的能力

1
2
3
4
5
6
7
8
import { reactive } from 'vue';

const state = reactive({
count: 0
});

// 修改 state.count 会触发视图更新
state.count++;

深层次响应式

Vue 的响应式不仅限于顶层属性它也支持深层次的数据结构如对象和数组这意味着你可以在嵌套的对象或数组中进行修改并且这些修改也会触发相应的视图更新

1
2
3
4
5
6
7
8
9
10
import { reactive } from 'vue';

const state = reactive({
nested: {
count: 0
}
});

// 修改 state.nested.count 也会触发更新
state.nested.count++;

注意事项


⚠️新增和删除属性在 Vue 2 中直接向响应式对象添加或删除属性不会触发视图更新Vue 3 使用 Proxy 后解决了这个问题但如果你使用的是 Vue 2则需要使用 Vue.set 或 Vue.delete 来确保响应性
⚠️循环引用对于复杂的嵌套对象结构可能会出现循环引用的情况这时需要注意避免无限递归的问题
⚠️异步更新队列Vue 实现了一个异步更新队列用于批量处理 DOM 更新提高性能因此即使多次更改同一个属性DOM 也可能只更新一次

开发者工具

在 Vue.js 开发中开发者工具是一个非常强大的辅助工具Vue Devtools 是由 Vue 官方维护的一款浏览器扩展插件目前支持 Chrome 和 Firefox 浏览器它与 Vue 2.x 和 Vue 3.x 都兼容它可以帮助你

  • 查看组件树结构
  • 检查组件状态datapropscomputed 等
  • 调试 Vuex 状态管理
  • 监控事件和生命周期
  • 时间旅行调试Time Travel Debugging

下载安装使用

  1. 下载地址https://chrome.zzzmh.cn/search/Vue选择 Vue.js Devtools 点击下载
  2. 安装解压下载好的安装包 → 打开谷歌浏览器 → 安装扩展程序 → 把安装包中后缀名为 crx 的插件拖入浏览器
  3. 配置安装完成后 → 点击详情 → 允许访问文件网址
  4. 使用安装成功后打开任意 Vue 项目页面F12 或 右键点击检查打开浏览器开发者工具在顶部标签栏中会多出一个 “Vue” 标签页

Vue 配置

创建 Vue 应用实例时需要传入选项对象这个对象包含了应用的各种配置和生命周期钩子用于定义组件的行为数据方法等

1
2
3
4
5
6
7
8
9
10
11
12
13
配置对象可以包含很多选项比如
el指定挂载点即该 Vue 实例控制的 DOM 容器
data数据对象用于响应式绑定
methods定义可以在模板中调用的方法
computed计算属性基于已有数据进行逻辑处理后返回新值具有缓存机制
watch监听数据变化
template自定义模板提供了 template则会替换掉挂载点的内容
生命周期钩子函数
created()实例已经创建完成但还未挂载到 DOM 上
mounted()DOM 已经渲染完成
updated()数据更新导致视图更新后调用
destroyed()实例销毁时调用
等等...

el

el 选项用于指定一个页面上已存在的 DOM 元素Vue 实例将管理这个元素及其子元素它通常用于将 Vue 应用挂载到网页的特定部分

基本概念

作用el 是 Vue 实例的一个选项用来指定控制的 DOM 元素Vue 会把这个元素作为应用的根容器并替换其内容
值类型可以是一个 CSS 选择器字符串或者直接是一个实际的 DOM 元素对象

使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
<div id="app"></div>
<script>
//可以是CSS选择器字符串
new Vue({
el: '#app'
});

//或者使用实际的 DOM 元素对象
var element = document.getElementById('app');
new Vue({
el: element
});
</script>

data

用于定义组件内部的状态即响应式数据Vue 会追踪 data 对象中所有属性的依赖并在这些属性发生变化时自动更新视图

基本概念

作用data 选项是一个函数尤其在组件中它返回一个对象这个对象包含了组件的所有响应式数据
响应性Vue 自动将 data 函数返回的对象中的所有属性转化为响应式这意味着当这些属性的值发生改变时Vue 能够检测到这些变化并相应地更新 DOM

使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<div id="app">
<h1>{{ title }}</h1>
<p>欢迎你{{ user.name }}</p>
<p>当前时间{{ new Date() }}</p>
</div>

<script>
new Vue({
el: '#app',
data: {
title: 'Vue 插值表达式示例',
user: {
name: '张三'
}
}
})
</script>

methods

methods 用于定义组件内的方法这些方法可以包含事件处理逻辑计算属性之外的复杂操作以及其他需要执行的函数Vue 会自动将 methods 中的方法绑定到 Vue 实例上使得它们可以直接通过实例来访问并且在模板中也可以直接调用

基本概念

作用methods 用于定义可以在 Vue 组件内使用的函数这些函数可以响应用户输入如点击按钮执行业务逻辑或与组件的状态交互
特点方法中的 this 关键字指向当前 Vue 实例因此可以直接访问和修改组件的数据 (data) 和其他方法Vue 自动将 methods 绑定到 Vue 实例确保正确的作用域

使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<div id="app">
<p>{{ message }}</p>
<button @click="greet">Greet</button>
<button @click="increment">Clicked {{ count }} times.</button>
</div>
<script>
new Vue({
el: '#app',
data: {
message: 'Hello Vue!',
count: 0
},
methods: {
greet() {
alert(this.message);
},
increment() {
this.count++;
}
}
})
</script>

computed

computed计算属性是一种非常强大且常用的特性用于声明依赖响应式数据的复杂逻辑或派生值它本质上是一个带有缓存机制的函数当其依赖的数据未发生变化时计算属性会直接返回上一次的缓存结果而不会重新执行函数

基本概念

computed 是一个对象包含若干个函数每个函数的返回值会被当作组件的一个虚拟属性使用这些属性是响应式的并且具有缓存机制

使用方法

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
<div id="app">
<p>全名{{ fullName }}</p>
</div>
<script>
new Vue({
el: '#app',
data: {
firstName: '张',
lastName: '三'
},
computed: {
//简单写法
fullName() {
return this.firstName + ' ' + this.lastName;
},
//用于设置值时的写法
fullName1: {
// Getter用于获取 fullName
get() {
return this.firstName + ' ' + this.lastName;
},
// Setter用于设置 fullName
set(newValue) {
const names = newValue.split(' ');
this.firstName = names[0];
this.lastName = names[1] || '';
}
}
}
})
</script>

watch

watch 选项用于监听特定数据的变化并在这些数据发生变化时执行相应的回调函数它非常适合处理需要对数据变化做出响应的场景特别是那些涉及异步操作或开销较大的操作

基本概念

作用监听 Vue 实例中的数据变化包括 data, computed 属性等并在侦听到变化时执行自定义逻辑
适用场景当你需要在某个数据改变后执行一些副作用操作如网络请求DOM 操作等时非常有用

使用方法

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
<script>
new Vue({
el: '#app',
data: {
question: '',
answer: 'Questions usually contain a question mark. ;-)'
},
methods: {
getAnswer() {
setTimeout(() => {// 模拟异步操作
this.answer = 'Got an answer.';
}, 1000);
}
},
watch: {
// 如果 `question` 发生变化这个回调函数就会运行
question(newQuestion, oldQuestion) {
console.log(`问题从 "${oldQuestion}" 变为 "${newQuestion}"`);
this.answer = 'Waiting for you to stop typing...';
// 这里可以进行异步操作
this.getAnswer();
}
}
})
</script>

立即触发

有时候你可能希望在监听开始时立即执行一次回调函数可以通过设置 immediate: true 来实现

1
2
3
4
5
6
7
8
9
10
new Vue({
watch: {
question: {
handler(newQuestion, oldQuestion) {
console.log("立即触发");
},
immediate: true
}
}
})

深度监听

如果要监听的是对象内部的变化而不是引用本身的改变可以使用深度监听

1
2
3
4
5
6
7
8
9
10
new Vue({
watch: {
someObject: {
handler(newVal, oldVal) {
console.log("深度监听");
},
deep: true
}
}
})

生命周期

Vue 组件的生命周期钩子允许你在组件的不同阶段执行自定义逻辑

钩子函数

Vue 3 中生命周期钩子与 Vue 2 基本相同但有些钩子名称略有变化

钩子Vue 2 钩子Vue 3 描述
beforeCreate onBeforeMount
(部分功能)
实例初始化之后数据观测(data observer) 和 event/watcher 事件配置之前被调用在 Vue 3 Composition API 中这个阶段通常不需要特别处理
created onMounted
(部分功能)
实例创建完成后被调用此时实例已完成以下的配置数据观测(data observer)属性和方法的运算watch/event 事件回调然而挂载阶段还没开始$el 属性目前不可见
beforeMount onBeforeMount 在挂载开始之前被调用相关的 render 函数首次被调用
mounted onMounted 实例已挂载后调用这时 el 被新创建的 vm.el替换如果根实例挂载到了一个文档内的元素上当mounted被调用时vm.el替换如果根实例挂载到了一个文档内的元素上当mounted被调用时vm.el 也在文档内
beforeUpdate onBeforeUpdate 数据更新时调用发生在虚拟 DOM 打补丁之前这里适合在更新之前访问现有的 DOM比如手动移除已添加的事件监听器
updated onUpdated 由于数据更改导致的虚拟 DOM 重新渲染和打补丁在这之后会调用该钩子当这个钩子被调用时组件 DOM 已经更新所以你现在可以执行依赖于 DOM 的操作
beforeDestroy onBeforeUnmount 由于数据更改导致的虚拟 DOM 重新渲染和打补丁在这之后会调用该钩子当这个钩子被调用时组件 DOM 已经更新所以你现在可以执行依赖于 DOM 的操作
destroyed onUnmounted Vue 实例销毁后调用调用后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
<div id="app">
<p>{{ message }}</p>
</div>
<script>
new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
},
beforeCreate() {
console.log('实例初始化之后调用');
},
created() {
console.log('实例创建完成之后调用');
},
beforeMount() {
console.log('实例挂载之前调用');
},
mounted() {
console.log('实例挂载之后调用');
},
beforeUpdate() {
console.log('数据发生改变后DOM 被更新之前被调用');
},
updated() {
console.log('更新完毕之后调用');
},
beforeDestroy() {
console.log('实例销毁之前调用');
},
destroyed() {
console.log('实例销毁之后调用');
}
})
</script>

Vue 指令

指令系统是 Vue.js 核心特性之一通过这些指令可以非常方便地操作 DOM绑定数据处理用户输入等指令通常以 v- 开头它们提供了丰富的功能来增强 Vue 应用的能力

基础渲染指令

v-text

1
2
3
4
<!-- 用于更新元素的文本内容 -->
<span v-text="message"></span>
<!-- 相当于 -->
<span>{{ message }}</span>

v-html

1
2
3
4
5
6
7
8
<!-- 用于输出真正的 HTML 内容而不是纯文本 -->
<!-- 注意使用时要确保内容安全防止 XSS 攻击 -->
<p v-html="rawHtml"></p>
data: {
rawHtml: '<strong>加粗文字</strong>'
}
<!-- 相当于 -->
<p><strong>加粗文字</strong></p>

v-once

1
2
3
4
5
6
<!-- 指定某个元素或组件只渲染一次并且之后不会随着数据的变化而重新渲染 -->
<span v-once>{{ message }}</span>
<div v-once>
<h1>{{ title }}</h1>
<p>{{ description }}</p>
</div>

属性绑定指令

v-bind

用于动态绑定 HTML 属性值如 hrefsrcclassstyle 等

⭐⭐ 绑定属性

1
2
3
4
<!-- 语法 -->
<a v-bind:href="url">链接</a>
<!-- 可以简写为:属性 -->
<a :href="url">链接</a>

⭐⭐ 绑定类名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- 对象语法 -->
<div :class="{ active: isActive, 'text-danger': false }">
动态类名
</div>

<!-- 数组语法 -->
<div :class="[activeClass, errorClass]">
动态类名
</div>

<!-- 结合对象和数组 -->
<div :class="[{ active: isActive }, errorClass]"></div>

<!-- Vue.js 数据定义 -->
data: {
isActive: true,
activeClass: 'active',
errorClass: 'text-danger'
}

⭐⭐ 绑定样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- 对象语法 -->
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }">
动态内联样式
</div>

<!-- 驼峰命名 -->
<div :style="{ color: 'blue', fontSize: '14px', marginTop: '20px' }"></div>

<!-- 短横线命名 -->
<div :style="{ color: 'blue', 'font-size': '14px', 'margin-top': '20px' }"></div>

<!-- 数组语法多个 style 对象合并 -->
<div :style="[baseStyles, overridingStyles]"></div>

<!-- Vue.js 数据定义 -->
data: {
baseStyles: { color: 'black', fontSize: '16px' },
overridingStyles: { color: 'red' }
}

⭐⭐ 高级技巧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- 动态计算结合 computed 属性可以让逻辑更清晰 -->
<div :class="dynamicClasses"></div>
computed: {
dynamicClasses() {
return {
active: this.isActive,
disabled: !this.isAvailable
}
}
}

<!-- 模块化使用 SCSS/CSS Modules模块化 CSS -->
<template>
<div :class="$style.myClass">模块化样式</div>
</template>

<style module>
.myClass {
color: blue;
}
</style>

v-model

实现表单输入和应用状态之间的双向数据绑定

⭐⭐ 文本输入框

1
2
3
<!-- 文本输入框 -->
<input v-model="message" placeholder="输入一些文本">
<p>消息是: {{ message }}</p>

⭐⭐ 多行文本

1
2
3
<!-- 多行文本 -->
<textarea v-model="message"></textarea>
<p>多行消息是: {{ message }}</p>

⭐⭐ 单选按钮

1
2
3
4
<!-- 单选按钮 -->
<input type="radio" id="one" value="One" v-model="picked">
<input type="radio" id="two" value="Two" v-model="picked">
<span>Picked: {{ picked }}</span>

⭐⭐ 复选框

1
2
3
4
5
6
7
8
<!-- 单一复选框布尔值 -->
<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">{{ checked }}</label>

<!-- 多个复选框数组 -->
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<span>Checked names: {{ checkedNames }}</span>

⭐⭐ 下拉框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- 单选下拉框 -->
<select v-model="selected">
<option value="A">A</option>
<option value="B">B</option>
<option value="C">C</option>
</select>
<span>Selected: {{ selected }}</span>

<!-- 多选下拉框 -->
<select v-model="selected" multiple>
<option value="A">A</option>
<option value="B">B</option>
<option value="C">C</option>
</select>
<span>Selected: {{ selected }}</span>

流程控制指令

v-if

1
2
<!-- 根据表达式的真假值决定是否渲染该元素 -->
<p v-if="seen">现在你看到我了</p>

v-else

1
2
3
<!-- 与 v-if 配合使用表示否则的情况 -->
<p v-if="seen">现在你看到我了</p>
<p v-else>现在你看不到我</p>

v-else-if

1
2
3
4
<!-- 作为 v-if 的扩展提供更多的条件分支 -->
<div v-if="type === 'A'">优秀</div>
<div v-else-if="type === 'B'">良好</div>
<div v-else>一般</div>

v-for

1
2
3
4
5
6
7
8
9
10
<!-- 用于循环渲染列表 -->
<ul>
<!-- 遍历数组 -->
<li v-for="(item, index) in items" :key="index">{{ item }}</li>
</ul>

<ul>
<!-- 遍历对象 -->
<li v-for="(value, key, index) in object">{{ key }}: {{ value }}</li>
</ul>

事件处理指令

v-on

1
2
3
4
<!-- 用于监听 DOM 事件并在触发时执行 JavaScript 代码 -->
<button v-on:click="js函数名">点击我</button>
<!-- 简写形式为 @ -->
<button @click="js函数名">点击我</button>

其他常用指令

v-show

1
2
3
<!-- 根据表达式的真假值显示或隐藏元素 -->
<!-- 与 v-if 不同的是它只是简单地切换 CSS 中的 display 属性 -->
<p v-show="isVisible">现在你看到我了</p>

v-pre

1
2
3
<!-- 跳过这个元素和它的子元素的编译过程 -->
<!-- 可以用来显示原始 Mustache 标签 -->
<p v-pre>{{ 这个不会编译 }}</p>

v-cloak

1
2
3
4
5
6
7
8
9
10
11
<!-- 防止未编译的 Mustache 标签在页面加载时闪现 -->
<!-- 直到 Vue 实例完全初始化后才会显示相应的数据 -->
<!-- 定义一个 CSS 规则来隐藏带有 v-cloak 属性的元素 -->
<style>
[v-cloak] {
display: none;
}
</style>
<div v-cloak>
{{ message }}
</div>

自定义指令

除了内置指令外Vue 还允许开发者自定义指令

钩子函数

⭐⭐ 一个自定义指令对象可以包含以下钩子函数可选

钩子函数 触发时机 函数说明
beforeMount 指令第一次绑定到元素时调用只执行一次初始化前
mounted 指令绑定的元素被插入到 DOM 时调用初始化完成
beforeUpdate 在元素更新之前调用每次数据变更都会触发更新前
updated 在元素更新完成后调用更新后
beforeUnmount 指令即将从元素上解绑前调用销毁前
unmounted 指令从元素上解绑后调用销毁完成

⭐⭐ 每个钩子函数可以接收以下参数可选

可选参数 参数说明
el 绑定的 DOM 元素
binding 包含指令相关信息的对象name, value, oldValue, arg, modifiers
vnode 虚拟节点VNode
prevNode 上一个 VNode仅在 beforeUpdate 和 updated 中可用

⭐⭐ binding 对象属性

1
2
3
4
5
6
7
8
{
name: 'focus', // 指令名
value: 'admin', // 指令的值表达式结果
oldValue: undefined, // 上一个值
arg: 'role', // 指令参数如 v-example:role
modifiers: { bold: true }, // 修饰符如 v-example.bold
instance: component // 当前组件实例
}

注册指令

⭐⭐ 全局注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// main.js
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

// 注册一个全局自定义指令 v-focus
app.directive('focus', {
mounted(el) {
el.focus()//获取焦点
}
})

app.mount('#app')

⭐⭐ 局部注册

1
2
3
4
5
6
7
8
9
export default {
directives: {
focus: {
mounted(el) {
el.focus()//获取焦点
}
}
}
}

使用示例

⭐⭐ 示例1自动聚焦Focus

1
2
3
4
5
6
7
app.directive('focus', {
mounted(el) {
// 页面渲染完成后自动聚焦该元素
el.focus()
}
})
<input v-focus />

⭐⭐ 示例2权限高亮根据用户角色动态设置背景色

1
2
3
4
5
6
7
8
9
10
app.directive('highlight', {
mounted(el, binding) {
const roles = ['admin', 'editor']
if (roles.includes(binding.value)) {
el.style.backgroundColor = '#f0e68c' // 浅黄色
}
}
})

<div v-highlight="'admin'">管理员内容</div>

⭐⭐ 示例3节流滚动监听防抖/节流优化性能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
app.directive('throttle-scroll', {
mounted(el, binding) {
let ticking = false
window.addEventListener('scroll', () => {
if (!ticking) {
requestAnimationFrame(() => {
binding.value(window.scrollY)
ticking = true
})
}
setTimeout(() => {
ticking = false
}, 200)
})
}
})

<div v-throttle-scroll="(y) => scrollPosition = y"></div>

⭐⭐ 示例4拖动指令实现简单拖拽功能

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
app.directive('draggable', {
mounted(el) {
let offsetX = 0, offsetY = 0

el.style.position = 'absolute'
el.style.cursor = 'move'

el.addEventListener('mousedown', (e) => {
offsetX = e.clientX - el.offsetLeft
offsetY = e.clientY - el.offsetTop

function onMouseMove(e) {
el.style.left = `${e.clientX - offsetX}px`
el.style.top = `${e.clientY - offsetY}px`
}

function onMouseUp() {
document.removeEventListener('mousemove', onMouseMove)
document.removeEventListener('mouseup', onMouseUp)
}

document.addEventListener('mousemove', onMouseMove)
document.addEventListener('mouseup', onMouseUp)
})
}
})

<div v-draggable style="width: 100px; height: 100px; background: lightblue;">拖我</div>

⭐⭐ 示例5权限控制显示根据角色决定是否渲染元素

1
2
3
4
5
6
7
8
9
app.directive('permission', {
beforeMount(el, binding) {
const userRoles = ['admin'] // 假设当前用户是 admin
if (!userRoles.includes(binding.value)) {
el.parentNode.removeChild(el)
}
}
})
<button v-permission="'admin'">删除</button>

指令修饰符

指令修饰符是对指令功能的增强通过在指令后加上特定的后缀来改变其行为

事件修饰符

v-on 指令用于监听 DOM 事件并在触发时执行 JavaScript 代码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
<!-- 阻止事件冒泡 -->
<!-- 点击按钮只会触发 doSomethingElse 方法而不会触发外层 div 的 doSomething 方法 -->
<div @click="doSomething">
<button @click.stop="doSomethingElse">点击我</button>
</div>

<!-- 使用事件捕获模式添加事件监听器 -->
<!-- 点击按钮时首先会触发 div 上的 doSomething 方法然后再触发按钮上的 doSomethingElse 方法 -->
<div @click.capture="doSomething">
<button @click="doSomethingElse">点击我</button>
</div>

<!-- 只有当事件是从监听器绑定的元素本身触发时才触发回调 -->
<!-- 直接点击 div 才会触发 doSomething 方法而点击按钮则不会 -->
<div @click.self="doSomething">
<button @click="doSomethingElse">点击我</button>
</div>

<!-- 只触发一次事件处理程序 -->
<!-- 无论用户点击多少次按钮doSomething 方法只会被调用一次 -->
<button @click.once="doSomething">点击我</button>

<!-- 阻止默认行为 -->
<!-- 这将阻止表单提交时页面刷新的默认行为 -->
<form @submit.prevent="onSubmit">
<!-- 表单内容 -->
</form>

<!-- 告诉浏览器你不想阻止事件的默认行为通常用于滚动优化 -->
<div @touchmove.passive="onTouchMove">...</div>

表单修饰符

除了 v-on 指令修饰符外Vue 还为 v-model 提供了一些修饰符用于处理常见的表单输入情况

1
2
3
4
5
6
7
8
9
<!-- 默认情况下v-model 在每次 input 事件触发时同步输入框的值到数据 -->
<!-- .lazy 修饰符让其改为在 change 事件之后进行同步 -->
<input v-model.lazy="message" />

<!-- 自动将用户的输入值转换为数值类型 -->
<input v-model.number="age" type="number">

<!-- 自动过滤用户输入的首尾空白字符 -->
<input v-model.trim="message" />

按键修饰符

当你监听键盘事件时可以使用按键修饰符来监听特定键的按下

1
2
3
4
5
6
7
8
9
10
11
<!-- 常见按键修饰符包括: .enter | .tab | .delete | .esc | .space | .up | .down | .left | .right -->

<!-- 监听回车键 -->
<input @keyup.enter="submit">

<!-- 使用 keyCode 来指定具体的键 -->
<input @keyup.13="submit">

<!-- 使用自定义别名来指定具体的键 -->
Vue.config.keyCodes.f1 = 112
<input @keyup.f1="doSomething">

系统修饰键

可以使用以下系统修饰键来触发鼠标或键盘事件监听器

1
2
3
4
<!-- .ctrl | .alt | .shift | .meta (在 Mac 键盘上是 Command 键在 Windows 键盘上是 Windows 键) -->

<!-- 同时按下 Ctrl 键和点击才能触发事件 -->
<div @click.ctrl="onClick">需要同时按Ctrl键点击</div>

Vue 组件

Vue.js 其核心特性之一是组件化开发组件允许我们将 UI 拆分为独立的可复用的部分使代码更易维护和扩展

创建项目

1
2
3
4
5
6
7
8
# 安装 VUE 脚手架只需要安装一次
npm install -g @vue/cli

# 创建项目(projectName不能是中文)
vue create projectName

# 运行项目
npm run serve

组成部分

⭐⭐ 模板 (Template)

模板定义了组件的结构和布局它使用 HTML 标记来描述组件的 UI并且可以包含 Vue 的指令如 v-if, v-for 等和插值语法 () 来实现动态内容

1
2
3
4
5
6
<template>
<div class="example">
<h1>{{ title }}</h1>
<p>{{ content }}</p>
</div>
</template>

⭐⭐ 脚本 (Script)

脚本部分包含了组件的逻辑包括数据方法生命周期钩子等它是用 JavaScript 编写的负责处理组件的行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<script>
export default {
data() {
return {
title: '我的标题',
content: '这是一个例子内容'
}
},
methods: {
updateContent(newContent) {
this.content = newContent;
}
},
computed: {
fullText() {
return `${this.title} - ${this.content}`;
}
},
mounted() {
console.log('组件已挂载');
}
}
</script>

⭐⭐ 样式 (Style)

样式部分允许你为组件添加 CSS 样式Vue 支持单文件组件中的样式定义你可以选择是否将这些样式作用于全局或仅限于当前组件通过 scoped 属性

1
2
3
4
5
6
7
<!-- scoped属性用于标记该样式为私有样式不添加该属性则为全局样式 -->
<style scoped>
.example {
font-family: Arial, sans-serif;
color: #333;
}
</style>

组件注册

全局组件

全局组件可以在整个应用程序中使用

1
2
3
4
5
6
7
8
9
10
11
12
// main.js
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

// 定义一个全局组件
app.component('my-component', {
template: `<div>这是一个全局组件</div>`
})

app.mount('#app')

局部组件

局部组件只能在定义它的父组件中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- ParentComponent.vue -->
<template>
<div>
<h1>父组件</h1>
<ChildComponent />
</div>
</template>

<script>
import ChildComponent from './ChildComponent.vue'

export default {
components: {
ChildComponent
}
}
</script>

组件通信

父子组件通信

父组件通过 props 向子组件传递数据

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
<!-- ChildComponent.vue -->
<template>
<div>
<p>来自父组件的消息{{ message }}</p>
</div>
</template>

<script>
export default {
props: ['message']
}
</script>


<!-- ParentComponent.vue -->
<template>
<div>
<h1>父组件</h1>
<ChildComponent :message="parentMessage" />
</div>
</template>

<script>
import ChildComponent from './ChildComponent.vue'

export default {
components: {
ChildComponent
},
data() {
return {
parentMessage: '你好子组件'
}
}
}
</script>

子父组件通信

子组件通过 $emit 触发事件父组件监听该事件

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
<!-- ChildComponent.vue -->
<template>
<button @click="sendToParent">点击发送消息给父组件</button>
</template>

<script>
export default {
methods: {
sendToParent() {
this.$emit('child-event', '这是来自子组件的消息')
}
}
}
</script>

<!-- ParentComponent.vue -->
<template>
<div>
<h1>父组件</h1>
<ChildComponent @child-event="handleChildEvent" />
<p>收到子组件的消息{{ childMessage }}</p>
</div>
</template>

<script>
import ChildComponent from './ChildComponent.vue'

export default {
components: {
ChildComponent
},
data() {
return {
childMessage: ''
}
},
methods: {
handleChildEvent(message) {
this.childMessage = message
}
}
}
</script>

非父子组件通信

非父子组件使用事件总线Event Bus通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 创建一个空的 Vue 实例作为事件总线
// event-bus.js
import { createApp } from 'vue'
export const EventBus = createApp({})

// 发送事件
import { EventBus } from './event-bus.js'
EventBus.config.globalProperties.$emit('custom-event', data)

// 监听事件
import { EventBus } from './event-bus.js'
EventBus.config.globalProperties.$on('custom-event', (data) => {
console.log('接收到数据:', data)
})

跨层级通信

provide / inject 用于跨层级传递数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- ParentComponent.vue -->
<script>
export default {
provide() {
return {
theme: 'dark'
}
}
}
</script>

<!-- ChildComponent.vue -->
<script>
export default {
inject: ['theme']
}
</script>

组件分类

根组件

根组件是整个 Vue 应用的入口点也是其他所有组件的父组件它通常与一个 DOM 元素绑定并且作为应用的起点包含整个应用的主要逻辑状态管理等

⭐⭐ 特性

唯一性每个 Vue 应用都有且仅有一个根组件
挂载点根组件通过 app.mount('#app') 与 HTML 中的一个元素<div id="app"></div>绑定
全局资源注册可以在根组件创建后注册全局组件指令过滤器等
状态管理如果使用 Vuex 或 Pinia 等状态管理库通常会在根组件或其附近初始化这些库

⭐⭐ main.js

1
2
3
4
5
6
7
8
9
10
11
12
// main.js
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

// 注册全局组件指令等
app.component('global-component', {
template: '<div>这是一个全局组件</div>'
})

app.mount('#app')

⭐⭐ 根组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- App.vue - 根组件 -->
<template>
<div id="app">
<h1>{{ message }}</h1>
<global-component />
</div>
</template>

<script>
export default {
data() {
return {
message: '欢迎来到我的 Vue 应用'
}
}
}
</script>

普通组件

普通组件是指除根组件之外的所有组件它们可以嵌套使用形成组件树结构普通组件有助于实现代码复用提高开发效率和维护性

⭐⭐ 特性

可复用性设计良好的普通组件可以在不同的地方重复使用减少代码冗余
局部注册普通组件可以通过局部注册的方式在需要使用的父组件中注册和使用
Props 和 Events普通组件之间以及普通组件与父组件之间主要通过 Props 和自定义事件进行通信

⭐⭐ 子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- ChildComponent.vue -->
<template>
<div>
<p>{{ message }}</p>
<button @click="$emit('reply', '这是来自子组件的回复')">回复</button>
</div>
</template>

<script>
export default {
props: ['message']
}
</script>

⭐⭐ 父组件

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
<!-- ParentComponent.vue -->
<template>
<div>
<h1>父组件</h1>
<child-component :message="parentMessage" @reply="handleReply"/>
</div>
</template>

<script>
import ChildComponent from './ChildComponent.vue'

export default {
components: {
ChildComponent
},
data() {
return {
parentMessage: '你好子组件'
}
},
methods: {
handleReply(reply) {
console.log(reply)
}
}
}
</script>

动态组件

动态组件允许你在同一个挂载点动态切换不同的组件而不必每次都销毁再重新创建它们这通过使用 <component> 元素和 is 属性来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<div>
<button @click="currentComponent = 'ComponentA'">加载组件 A</button>
<button @click="currentComponent = 'ComponentB'">加载组件 B</button>

<!-- 动态组件 -->
<component :is="currentComponent"></component>
</div>
</template>

<script>
import ComponentA from './ComponentA.vue'
import ComponentB from './ComponentB.vue'

export default {
components: { ComponentA, ComponentB },
data() {
return {
currentComponent: 'ComponentA'
}
}
}
</script>

异步组件

异步组件允许你按需加载组件有助于提高应用的初始加载性能

⭐⭐ 组件注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 全局注册异步组件
Vue.component('async-component', () => import('./myAsyncComponent.vue'))

// 局部注册异步组件
<template>
<div>
<h1>父组件</h1>
<async-component></async-component>
</div>
</template>

<script>
export default {
components: {
'async-component': () => import('./myAsyncComponent.vue')
}
}
</script>

⭐⭐ 错误处理

1
2
3
4
5
6
7
8
9
10
11
12
const AsyncComponent = () => ({
// 需要加载的组件 (应该是一个 `Promise` 对象)
component: import('./MyComponent.vue'),
// 加载中应当渲染的组件
loading: LoadingComponent,
// 出错时渲染的组件
error: ErrorComponent,
// 渲染加载中的组件前的等待时间默认值是 200 (毫秒)
delay: 200,
// 最长等待时间超出此时间则渲染错误组件默认值是 `Infinity`
timeout: 3000
});

组件缓存

在 Vue 中组件默认每次切换时都会销毁并重新创建但有时候我们需要保留组件状态比如在 Tab 切换路由切换等场景中避免重复渲染和数据加载Vue 提供了一个内置组件 <keep-alive> 来实现组件的缓存功能<keep-alive> 是一个抽象组件它包裹动态组件如通过 component is<router-view> 渲染的组件可以缓存这些组件的状态防止其被频繁销毁和重建

标签属性

⭐⭐ include

只有匹配的组件会被缓存可以是字符串指定组件名正则表达式或者数组形式当为数组时元素可以是组件名或正则表达式

1
2
3
4
5
6
7
8
9
<!-- 示例一 -->
<keep-alive include="a,b">
<component :is="view"></component>
</keep-alive>

<!-- 示例二 -->
<keep-alive :include="['a', 'b']">
<component :is="view"></component>
</keep-alive>

⭐⭐ exclude

匹配的组件不会被缓存可以是字符串指定组件名正则表达式或者数组形式当为数组时元素可以是组件名或正则表达式

1
2
3
4
5
6
7
8
9
<!-- 示例一 -->
<keep-alive exclude="a,b">
<component :is="view"></component>
</keep-alive>

<!-- 示例二 -->
<keep-alive :exclude="['a', 'b']">
<component :is="view"></component>
</keep-alive>

⭐⭐ max

设置缓存组中的最大组件数如果超出这个数量最早缓存的组件将被销毁

1
2
3
<keep-alive :max="10">
<component :is="view"></component>
</keep-alive>

控制缓存

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
<!-- 示例一 -->
<template>
<keep-alive>
<component :is="currentComponent" v-if="shouldRender" />
</keep-alive>
</template>

<script>
export default {
data() {
return {
currentComponent: 'TabA',
shouldRender: true,
};
},
};
</script>

<!-- 示例二 -->
<keep-alive>
<!-- 只缓存名为 TabA 的组件 -->
<component :is="currentComponent" v-if="currentComponent === 'TabA'" />

<!-- 缓存多个组件 -->
<component :is="currentComponent" v-if="currentComponent === 'TabB'" />
</keep-alive>

生命周期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div>我是缓存组件 {{ count }}</div>
</template>

<script>
export default {
data() {
return {
count: 0,
};
},
activated() {
console.log('当组件被 <keep-alive> 缓存 > 激活时调用');
},
deactivated() {
console.log('当组件被 <keep-alive> 缓存 > 停用时调用');
},
mounted() {
this.count++;
},
};
</script>

完整示例

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
<template>
<div>
<button @click="currentTab = 'TabA'">Tab A</button>
<button @click="currentTab = 'TabB'">Tab B</button>

<keep-alive>
<component :is="currentTab" v-if="currentTab === 'TabA'" />
</keep-alive>
<component :is="currentTab" v-if="currentTab === 'TabB'" />
</div>
</template>

<script>
export default {
data() {
return {
currentTab: 'TabA',
};
},
components: {
TabA: {
name: 'TabA',
template: `<div>这是 Tab A计数{{ count }} <button @click="count++">+1</button></div>`,
data() {
return {
count: 0,
};
},
activated() {
console.log('TabA 被激活');
},
deactivated() {
console.log('TabA 被缓存');
},
},
TabB: {
template: `<div>这是 Tab B</div>`,
},
},
};
</script>

组件进阶

v-model

在 Vue 中v-model 是一种语法糖用于实现双向绑定它本质上是结合了 props$emit 来实现父子组件之间的数据同步

⭐⭐ 语法糖

1
2
3
<input type="text" v-model="inputValue">
<!-- 等价于 -->
<input type="text" :value="inputValue" @input="inputValue = $event.target.value">

⭐⭐ 表单通信

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
<!-- 子组件CustomInput.vue -->
<template>
<input type="text" :value="value" @input="$emit('input', $event.target.value)"/>
</template>

<script>
export default {
props: ['value']
}
</script>

<!-- 父组件ParentComponent.vue -->
<template>
<div>
<CustomInput v-model="inputValue" />
</div>
</template>

<script>
import CustomInput from './CustomInput.vue'

export default {
components: { CustomInput },
data() {
return {
inputValue: ''
}
}
}
</script>

⭐⭐ sync 修饰符

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- CustomInput.vue -->
<template>
<input :value="text" @input="$emit('update:text', $event.target.value)"/>
</template>

<script>
export default {
props: ['text']
}
</script>

<!-- ParentComponent.vue -->
<CustomInput :text.sync="inputValue" />

自定义

默认情况下v-model 使用的 prop 名为 value触发的事件名为 input但在某些场景下我们可能需要自定义这个行为比如使用 modelValue 和 update:modelValue或者其它任意命名的 prop 和事件

Mixins

混合Mixins提供了一种灵活的方式用于分发 Vue 组件中的可复用功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- myMixin.js -->
<script>
export default {
created() {
console.log('混合对象的 created 钩子')
}
}
</script>

<!-- MyComponent.vue -->
<script>
import myMixin from './myMixin.js'

export default {
mixins: [myMixin],
created() {
console.log('组件自身的 created 钩子')
}
}
</script>

ref / $refs

在 Vue 中ref$refs 是用于直接访问和操作 DOM 元素或组件实例的重要机制虽然 Vue 鼓励使用数据驱动的方式处理视图但在某些场景下如聚焦输入框调用子组件方法等需要直接访问元素或组件实例这时就非常有用了

⭐⭐ 概念

  1. ref 是一个特殊的属性可以定义在普通的 HTML 元素或 Vue 组件上
  2. 它允许你为该元素或组件注册一个引用标识 在渲染完成后可以通过 $refs 对象来访问这些引用
  3. $refs 是 Vue 实例上的一个对象它持有所有通过 ref 注册的 DOM 元素或组件实例

⭐⭐ 普通 DOM 元素示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<div>
<input ref="myInput" type="text" />
<button @click="focusInput">聚焦输入框</button>
</div>
</template>

<script>
export default {
methods: {
focusInput() {
// 通过 $refs 访问 input 元素并调用 focus 方法
this.$refs.myInput.focus();
}
}
}
</script>

⭐⭐ 子组件引用示例

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
<!-- 子组件ChildComponent.vue -->
<template>
<div>我是子组件</div>
</template>

<script>
export default {
methods: {
sayHello() {
console.log('Hello from child component!');
}
}
}
</script>

<!-- 父组件ParentComponent.vue -->
<template>
<div>
<ChildComponent ref="childRef" />
<button @click="callChildMethod">调用子组件方法</button>
</div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
components: { ChildComponent },
methods: {
callChildMethod() {
// 调用子组件的方法
this.$refs.childRef.sayHello();
}
}
}
</script>

$nextTick

由于 Vue 的响应式更新是异步的即数据变化后不会立即更新 DOM而是将更新放入一个队列中在下一个事件循环tick中批量更新因此我们不能直接在数据修改后立即访问更新后的 DOM为了解决这个问题Vue 提供了 $nextTick() 方法用于在DOM 更新之后执行某些操作

⭐⭐ 概念

作用 在下一次 DOM 更新循环结束后执行回调函数
使用场景 当你修改了数据并希望在 DOM 更新完成后进行一些操作如获取 DOM 尺寸手动聚焦调用第三方插件等时使用

⭐⭐ 基本语法

1
2
3
4
5
6
7
8
// 第一种写法函数式
this.$nextTick(() => {
// 此处可以安全地访问更新后的 DOM
});

// 第二种写法async/await
await this.$nextTick();
// 此处可以安全地访问更新后的 DOM

⭐⭐ 示例一数据更新后聚焦输入框

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
<template>
<div>
<input v-if="showInput" ref="myInput" type="text" />
<button @click="showAndFocus">显示并聚焦输入框</button>
</div>
</template>

<script>
export default {
data() {
return {
showInput: false
};
},
methods: {
showAndFocus() {
this.showInput = true;

// DOM 还未更新此时 $refs.myInput 是 undefined
// 使用 $nextTick 等待 DOM 更新后再聚焦
this.$nextTick(() => {
this.$refs.myInput.focus();
});
}
}
};
</script>

⭐⭐ 示例 2修改数据后获取 DOM 内容

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
<template>
<div>
<p ref="textContent">{{ message }}</p>
<button @click="changeMessage">更改消息并读取内容</button>
</div>
</template>

<script>
export default {
data() {
return {
message: '原始信息'
};
},
methods: {
changeMessage() {
this.message = '新信息';

// 如果不使用 $nextTick获取到的内容还是旧的
this.$nextTick(() => {
console.log(this.$refs.textContent.innerText); // 输出新信息
});
}
}
};
</script>

Vue 插槽

在 Vue 中插槽是一种内容分发机制允许你在组件中插入任意的 HTML 或子组件并由父组件决定这些内容的具体形式插槽的本质是父组件向子组件传递内容的方式它不像 props 那样只能传递数据而是可以传递完整的结构HTML + 组件从而实现更复杂的 UI 组合

默认插槽

最简单的插槽形式也称为匿名插槽用于插入未命名的内容父组件可以在子组件标签内部放置任何 HTML 或 Vue 模板代码这些内容会替换子组件中的 <slot> 标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- 子组件 ChildComponent.vue -->
<template>
<div class="card">
<!-- 默认插槽 -->
<slot>这里是默认内容如果父组件没有传入内容则显示这个</slot>
</div>
</template>

<!-- 父组件 ParentComponent.vue -->
<template>
<ChildComponent>
<h2>这是插入到插槽中的内容</h2>
<p>来自父组件的自定义内容</p>
</ChildComponent>
</template>

具名插槽

当需要在组件中插入多个不同位置的内容时可以使用具名插槽通过给 <slot> 标签添加 name 属性来定义具名插槽这样父组件可以通过 <template v-slot:插槽名> 或简写的 #插槽名来为目标插槽提供特定内容

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
<!-- LayoutComponent.vue -->
<template>
<div class="layout">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot> <!-- 默认插槽 -->
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>

<!-- 父组件 ParentComponent.vue -->
<template>
<LayoutComponent>
<!-- 具名插槽 header -->
<template v-slot:header>
<h1>页面标题</h1>
</template>

<!-- 默认插槽 -->
<p>主要内容区域</p>

<!-- 具名插槽 footer -->
<template #footer>
<p>© 2025 我的网站. 版权所有.</p>
</template>
</LayoutComponent>
</template>

<!-- 使用 v-slot:name 指定插槽名称 -->
<!-- # 是 v-slot: 的缩写语法如 #footer 就等价于 v-slot:footer -->
<!-- <template> 标签仅作为逻辑容器不会渲染成实际 DOM 节点 -->

作用域插槽

作用域插槽允许父组件访问子组件中的数据通过这种方式父组件可以根据子组件提供的数据自定义渲染内容同时保持逻辑和模板的分离

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
<!-- ListComponent.vue -->
<template>
<ul>
<li v-for="item in items" :key="item.id">
<!-- 向外传递 item 数据 -->
<slot :item="item">{{ item.name }}</slot>
</li>
</ul>
</template>

<script>
export default {
data() {
return {
items: [
{ id: 1, name: '苹果' },
{ id: 2, name: '香蕉' },
{ id: 3, name: '橙子' }
]
};
}
};
</script>

<!-- 父组件 ParentComponent.vue -->
<template>
<ListComponent>
<!-- 接收子组件传来的 item -->
<template v-slot="{ item }">
<span style="color: red;">{{ item.name }}</span>
</template>
</ListComponent>
</template>

<!-- 子组件通过 <slot :item="item"> 把数据暴露出去 -->
<!-- 父组件通过 v-slot="{ item }" 接收数据并使用 -->
<!-- 可以同时绑定多个属性例如 <slot :item="item" :index="index"> -->

插槽解构

1
2
3
4
5
<template>
<ListComponent v-slot="{ item: { id, name }, index }">
<div>ID: {{ id }}, 名称: {{ name }}, 序号: {{ index }}</div>
</ListComponent>
</template>

Vue Router

Vue 路由主要指的是 Vue Router它是 Vue.js 官方的路由管理器它与 Vue.js 核心深度集成让构建单页面应用SPA变得非常容易

Route路由配置定义了 URL 和组件之间的映射关系
Router路由器实例负责解析 URL并将其匹配到相应的路由配置显示对应的组件
Link导航链接通过 <router-link> 组件来创建导航链接点击时不会重新加载页面

基本使用

安装路由

1
2
3
# vue2 对应 vue-router@3.x版本
# vue3 对应 vue-router@4.x版本
npm install vue-router@3.6.5

引入使用

⭐⭐ main.js

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
import Vue from 'vue'
import App from './App.vue'

// 引入组件
import Home from './components/XXHome.vue'
import About from './components/XXAbout.vue'

// 引入路由
import VueRouter from 'vue-router'
Vue.use(VueRouter)

// 配置路径
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/home',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: About
}
]

// 配置路由
const router = new VueRouter({
routes: routes
})

Vue.config.productionTip = false

new Vue({
render: h => h(App),
router: router
}).$mount('#app')

⭐⭐ app.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div id="app">
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
<style>
</style>

⭐⭐ 访问

http://localhost:8080/#/home

高亮导航

简单使用

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
<template>
<div id="app">
<!-- 使用 router-link 实现导航并通过 active-class 设置激活时的类 -->
<nav>
<router-link to="/">首页</router-link>
<router-link to="/home">home</router-link>
<router-link to="/about">about</router-link>
</nav>

<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
</template>

<script>
export default {
name: 'App'
}
</script>

<style>
/* 定义激活状态下链接的样式 */
.router-link-active {
font-weight: bold;
color: red;
}
</style>

自定义类名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- active-class自定义高亮类名 -->
<!-- 指定当此链接激活时应用的 CSS 类默认情况下它会添加名为 router-link-active 的类 -->
<nav>
<router-link to="/" active-class="active">首页</router-link>
<router-link to="/home" active-class="active">home</router-link>
<router-link to="/about" active-class="active">about</router-link>
</nav>

<style>
/* 定义激活状态下链接的样式 */
.active {
font-weight: bold;
color: red;
}
</style>

精确匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- exact精确匹配 -->
<!-- 对于根路径/来说如果不使用 exact那么所有以 / 开头的路径都会被认为是激活状态 -->
<nav>
<router-link to="/" active-class="active" exact>首页</router-link>
<router-link to="/home" active-class="active">home</router-link>
<router-link to="/about" active-class="active">about</router-link>
</nav>

<style>
/* 定义激活状态下链接的样式 */
.active {
font-weight: bold;
color: red;
}
</style>

模块拆分

在 Vue 项目中随着应用功能的增多路由配置routes会变得越来越复杂为了保持代码结构清晰易于维护将不同种类的文件拆分到多个独立文件中是一种常见的做法

模块拆分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
src/
├── components/ // 可复用组件文件夹
│ ├── button.vue // 按钮组件
│ └── alert.vue // 弹窗组件
├── router/ // 路由相关文件夹
│ ├── index.js // 主路由入口
│ ├── home.js // 首页相关路由
│ ├── user.js // 用户相关路由
│ └── settings.js // 设置相关路由
├── views/ // 视图相关组件文件夹
│ ├── Home.vue // 首页
│ ├── UserProfile.vue // 用户详情页
│ ├── UserList.vue // 用户列表页
│ └── Settings.vue // 系统设置页
├── app.vue
└── main.js

路径简写

在 Vue 项目中默认会将 @ 设置为 src 目录的别名

⭐⭐ 使用示例

1
2
3
import HelloWorld from '@/components/button.vue'
// 等价于
import HelloWorld from '../src/components/button.vue'

⭐⭐ 修改目录

可以通过 webpack.config.jsvue.config.js 文件中 resolve.alias 属性设置

1
2
3
4
5
6
7
8
9
10
// vue.config.js
module.exports = {
configureWebpack: {
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
}
}
}

路由配置

⭐⭐ 创建子路由文件

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
// src/router/home.js
export default [
{
path: '/',
name: 'Home',
component: () => import('../views/Home.vue'),
meta: { title: '首页' }
}
]

// src/router/user.js
export default [
{
path: '/user/list',
name: 'UserList',
component: () => import('../views/UserList.vue'),
meta: { title: '用户列表' }
},
{
path: '/user/profile/:id',
name: 'UserProfile',
component: () => import('../views/UserProfile.vue'),
meta: { title: '用户详情' }
}
]

// src/router/settings.js
export default [
{
path: '/settings',
name: 'Settings',
component: () => import('../views/Settings.vue'),
meta: { title: '系统设置' }
}
]

⭐⭐ 主路由合并所有子路由

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
// src/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

// 引入各个模块的路由配置
import homeRoutes from './home'
import userRoutes from './user'
import settingsRoutes from './settings'

// 合并所有路由
const routes = [
...homeRoutes,
...userRoutes,
...settingsRoutes,

// 可以添加404页面等其他通用路由
{
path: '*',
redirect: '/'
}
]

const router = new VueRouter({
routes: routes
})

export default router

⭐⭐ 在 main.js 中使用路由

1
2
3
4
5
6
7
8
import Vue from 'vue'
import App from './App.vue'
import router from './router'

new Vue({
render: h => h(App),
router: router
}).$mount('#app')

路由模式

Vue Router 支持两种主要的路由模式hash 模式和 history 模式这两种模式决定了应用如何处理 URL 以及如何在浏览器历史记录中进行导航

Hash 模式

工作原理hash 模式利用了 URL 中的 # 号即 hash来模拟一个完整的 URL当 # 后面的部分发生变化时页面不会重新加载Vue Router 默认使用的就是这种模式
优点兼容性好几乎所有的浏览器都支持不需要服务器端做任何配置因为 # 及其后面的内容不会被发送到服务器
缺点URL 看起来不太美观包含了一个 # 号

1
2
3
4
const router = new VueRouter({
mode: 'hash', // 默认就是 'hash' 模式可以省略不写
routes: routes
})

History 模式

工作原理history 模式依赖于 HTML5 History API 来实现 URL 导航而无需重新加载页面它允许你使用正常的路径格式/user/id而不是带 # 的路径
优点提供更传统且美观的 URL没有 #
缺点需要服务器端的支持因为如果用户直接访问或刷新页面服务器需要返回 index.html 文件否则会返回 404 错误

⭐⭐ 配置路由

1
2
3
4
const router = new VueRouter({
mode: 'history', // 使用 HTML5 History 模式
routes: routes
})

⭐⭐ 配置服务器端Apache

1
2
3
4
5
6
7
8
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</IfModule>

⭐⭐ 配置服务器端Nginx

1
2
3
location / {
try_files $uri $uri/ /index.html;
}

域名根目录

在 Vue Router 中base 配置项用于指定应用的基础 URL这个选项对于那些不是部署在域名根目录的应用特别有用例如当你的应用被部署在一个子路径https://example.com/my-app/你就可以使用 base 来配置这个基础路径

⭐⭐ 配置文件

1
2
3
4
5
//在项目的根目录下找到或创建 vue.config.js 文件
// vue.config.js
module.exports = {
publicPath: '/my-app/'
}

⭐⭐ 路由配置

1
2
3
4
5
6
7
8
const router = new VueRouter({
mode: 'history',
// process.env.BASE_URL 默认是 /
// 如果配置了vue.config.js
// 则使用 vue.config.js 中配置的 publicPath
base: process.env.BASE_URL,
routes: routes
})

⭐⭐ 服务端配置

1
2
3
location /my-app/ {
try_files $uri $uri/ /my-app/index.html;
}

路由重定向

路由重定向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const routes = [
// 默认首页
{ path: '/', name: 'Home', component: Home },

// 关于页面
{ path: '/about', name: 'About', component: About },

// 重定向从 '/home' 到 '/'
{ path: '/home', redirect: '/' },

// 动态重定向根据当前路径动态决定重定向目标
{ path: '/redirect-me', redirect: to => {
// 方法接收目标路由作为参数
return { path: '/about' }
}},

// 使用别名'/the-home-page' 是 '/' 的别名
{ path: '/', alias: '/the-home-page', component: Home }
]

配置404页面

⭐⭐ 创建 404 页面组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- src/views/NotFound.vue -->
<template>
<div class="not-found">
<h1>404 - 页面未找到</h1>
<p>对不起您访问的页面不存在</p>
</div>
</template>

<script>
export default {
name: "NotFound"
}
</script>

<style scoped>
.not-found {
text-align: center;
margin-top: 50px;
}
</style>

⭐⭐ 配置路由

1
2
3
4
5
6
7
8
9
10
11
// src/router/index.js
import Home from '../views/Home.vue'
import About from '../views/About.vue'
import NotFound from '../views/NotFound.vue' // 导入404页面组件
const routes = [
{ path: '/', component: Home, name: 'Home' },
{ path: '/about', component: About, name: 'About' },

// 最后添加404页面路由规则
{ path: '*', component: NotFound }
]

前进 / 后退

返回上一页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<button @click="goBack">返回上一页</button>
</template>

<script>
export default {
methods: {
goBack() {
//结合组件缓存使用效果会更好
//示例一this.$router.back();
//示例二this.$router.go(-1);
}
}
}
</script>

前进一页

1
this.$router.go(1); // 前进一页

自定义返回

1
2
3
4
5
6
7
8
// 可以判断是否存在上一页如果没有就跳转到默认页面
goBack() {
if (window.history.length > 1) {
this.$router.back();
} else {
this.$router.push('/'); // 跳转到首页
}
}

路由传参

路径参数

路径参数是 URL 中的一部分用于向目标路由传递参数例如/user/:id 中的 :id 就是一个路径参数

⭐⭐ 配置路由

1
2
3
4
5
6
7
const routes = [
{
path: '/user/:id', // :id 是路径参数
name: 'UserDetail',
component: UserDetail
}
]

⭐⭐ 传递参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- 声明式导航传参 -->
<!-- 通过 :to 绑定对象传递路径参数 -->
<router-link :to="{ name: 'UserDetail', params: { id: 123 }}">查看用户123</router-link>
<!-- 或者直接写路径 -->
<router-link to="/user/456">查看用户456</router-link>

<!-- 编程式导航传参 -->
<button @click="goToUser(789)">跳转到用户789</button>
<script>
export default {
methods: {
goToUser(id) {
// 编程式导航传参通过 push 方法跳转并传递路径参数
this.$router.push({
name: 'UserDetail', // 推荐使用 name
params: { id: id }
})
}
}
}
</script>

⭐⭐ 访问参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- src/views/UserDetail.vue -->
<template>
<div>
<h1>User Detail</h1>
<p>User ID: {{ $route.params.id }}</p> <!-- 直接从$route对象获取参数 -->
</div>
</template>

<script>
export default {
name: "UserDetail",
created() {
console.log(this.$route.params.id); // 另一种访问方式
}
}
</script>

查询参数

⭐⭐ 传递参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- 声明式导航传参 -->
<!-- 使用 query 属性传递查询参数 -->
<router-link :to="{ name: 'UserList', query: { search: 'vue', page: 2 } }">搜索 vue 用户第2页</router-link>
<!-- 或者直接写路径字符串 -->
<router-link to="/user/list?search=react&page=1">搜索 react 用户第1页</router-link>

<!-- 编程式导航传参 -->
<button @click="goToPage(1)">跳转到第一页</button>
<script>
export default {
methods: {
goToPage(page) {
this.$router.push({
name: 'UserList',
query: {
search: 'vue',
page: page
}
})
}
}
}
</script>

⭐⭐ 访问参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- UserList.vue -->
<template>
<div>
<h2>用户列表</h2>
<p>搜索关键词{{ $route.query.search }}</p>
<p>当前页码{{ $route.query.page }}</p>
</div>
</template>

<script>
export default {
name: 'UserList',
created() {
console.log('搜索词:', this.$route.query.search)
console.log('页码:', this.$route.query.page)
}
}
</script>

嵌套路由

嵌套路由是一种用于构建复杂用户界面的强大功能它允许你在一个父级路由下定义子路由从而创建多层次的页面布局

父级路由包含一个或多个子路由的路由
子路由依赖于父级路由的路由通常用于展示更详细的信息或者特定的功能模块

创建组件

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
<!-- src/views/ProductList.vue -->
<template>
<div>
<h1>产品列表</h1>
<ul>
<li v-for="product in products" :key="product.id">
<!-- 使用命名路由进行导航并传递路径参数 -->
<router-link :to="{ name: 'ProductDetail', params: { id: product.id }}">
{{ product.name }}
</router-link>
</li>
</ul>
<!-- 嵌套的 <router-view> 用于渲染子路由对应的组件 -->
<router-view></router-view>
</div>
</template>

<script>
export default {
data() {
return {
products: [
{ id: 1, name: '产品A' },
{ id: 2, name: '产品B' },
{ id: 3, name: '产品C' }
]
}
}
}
</script>

<!-- src/views/ProductDetail.vue -->
<template>
<div>
<h2>产品详情</h2>
<p>ID: {{ $route.params.id }}</p>
<!-- 这里可以根据 ID 获取并展示更多产品详情 -->
</div>
</template>

<script>
export default {
mounted() {
console.log('当前产品ID:', this.$route.params.id)
}
}
</script>

路由配置

children 数组用于定义子路由每个子路由对象都有自己的 pathname 和 component 属性
默认子路由如果你希望在访问父级路由路径时自动加载一个特定的子路由可以为空字符串定义一个子路由如 path: ‘’
懒加载组件为了提高性能可以对不常用的组件采用懒加载的方式例如 component: () => import(‘../views/…’)

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
// src/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import ProductList from '../views/ProductList.vue'
import ProductDetail from '../views/ProductDetail.vue'

Vue.use(VueRouter)

const routes = [
{
path: '/products',
component: ProductList,
// 定义子路由
children: [
{
// 默认子路由当访问 /products 时会渲染这个组件
path: '',
name: 'ProductList',
component: () => import('../views/ProductList.vue')
},
{
// 子路由匹配 /products/:id 路径
path: ':id',
name: 'ProductDetail',
component: ProductDetail
}
]
}
]

const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})

export default router

主应用组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- src/App.vue -->
<template>
<div id="app">
<nav>
<router-link to="/">首页</router-link>
<router-link to="/products">产品列表</router-link>
</nav>
<router-view></router-view>
</div>
</template>

<script>
export default {
name: 'App'
}
</script>

命名视图

命名视图允许你在同一层级渲染多个视图而不是嵌套它们这对于构建复杂的布局特别有帮助比如在一个页面上同时展示一个侧边栏和一个主内容区域要使用命名视图你需要在<router-view> 组件上指定一个 name 属性默认情况下没有指定 name<router-view> 会被认为是默认视图

创建组件

⭐⭐ Header.vue

1
2
3
4
5
6
7
8
9
10
11
12
<!-- Header.vue -->
<template>
<header>
<h1>这是头部</h1>
</header>
</template>

<script>
export default {
name: "Header"
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
<!-- Footer.vue -->
<template>
<footer>
<p>这是底部信息</p>
</footer>
</template>

<script>
export default {
name: "Footer"
}
</script>

⭐⭐ Home.vue

1
2
3
4
5
6
7
8
9
10
11
12
<!-- Home.vue -->
<template>
<div>
<p>这是首页内容</p>
</div>
</template>

<script>
export default {
name: "Home"
}
</script>

配置路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// router/index.js
import Vue from 'vue';
import Router from 'vue-router';

Vue.use(Router);

import Header from '@/components/Header.vue';
import Footer from '@/components/Footer.vue';
import Home from '@/views/Home.vue';
export default new Router({
routes: [
{
path: '/',
components: { // 注意这里是components而不是component
default: Home, // 默认视图对应Home组件
header: Header, // 命名视图为header的对应Header组件
footer: Footer // 命名视图为footer的对应Footer组件
}
}
]
});

主应用组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- App.vue -->
<template>
<div id="app">
<!-- 指定名字为header的router-view -->
<router-view name="header"></router-view>

<!-- 默认的router-view未指定name -->
<router-view></router-view>

<!-- 指定名字为footer的router-view -->
<router-view name="footer"></router-view>
</div>
</template>

<script>
export default {
name: "App"
}
</script>

前置守卫

Vue 全局前置守卫主要用于路由跳转前的拦截处理常用于登录验证等场景它允许你定义一个函数在每次导航时都会先调用这个函数以决定是否允许导航继续进行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 设置全局前置守卫
// to: 前往的路由对象
// from: 当前的路由对象
// next: 下一个路由
router.beforeEach((to, from, next) => {
const isAuthenticated = false; // 假设这里有一个逻辑判断用户是否已登录
if (to.path !== '/login' && !isAuthenticated) {
// 如果目标不是登录页并且用户未登录则重定向到登录页
next('/login');
} else {
// 否则允许导航继续执行
next();
}
});

Vuex

Vuex 是 Vue.js 的官方状态管理库它采用集中式存储管理应用的所有组件的状态并以相应的规则保证状态以一种可预测的方式发生变化

基本使用

安装Vuex

1
2
3
# vue2 对应 vuex@3.x版本
# vue3 对应 vuex@4.x版本
npm install vuex@3.6.2

创建仓库

1
2
3
4
5
6
7
8
9
10
11
12
13
// src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({
state: {
title: "Hello Vue"
}
})

export default store

引入仓库

1
2
3
4
5
6
7
8
9
// main.js
import Vue from 'vue';
import App from './App.vue';
import store from './store';

new Vue({
render: h => h(App),
store: store
}).$mount('#app')

使用仓库

1
2
3
4
5
<template>
<div id="app">
<h1>{{ $store.state.title }}</h1>
</div>
</template>

核心概念


State: 驱动应用的数据源
Getter: 从 state 中派生出一些状态类似于 Vue 组件中的计算属性
Mutation: 唯一可以改变 store 中状态的方法必须是同步函数
Action: 类似于 mutation不同在于它可以包含任意异步操作
Module: 将 store 分割成模块module每个模块拥有自己的 statemutationactiongetter

State

在 Vuex 中State 是存储应用层级状态的核心概念它就像一个全局的变量容器但与普通的全局变量不同的是Vuex 的 state 通过定义明确的规则来管理状态的变化这使得状态变化更加透明和可追踪State 是整个应用的数据源它是驱动应用行为的核心Vuex 将这些共享的状态集中存储在一个单一的 store 中并通过 Vue 的响应式系统使其具有响应性

单一状态树Vuex 使用单一状态树即用一个对象就包含了全部的应用层级状态这也意味着每个应用将仅仅包含一个 store 实例
响应式Vuex 中的状态是响应式的这意味着当 Vue 组件从 store 中读取状态的时候若 store 中的状态发生变化那么相应的组件也会相应地得到高效更新
访问状态可以在任何 Vue 组件中使用 this.$store.state 来访问 Vuex store 中的状态为了更好地组织代码通常会使用计算属性来映射 state

⭐⭐ 定义数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
export default new Vuex.Store({
state: {
// 基础类型
count: 0,

// 对象类型
user: {
name: 'Alice',
age: 25
},

// 数组类型
todos: [
{ id: 1, text: '学习 Vuex', done: true },
{ id: 2, text: '写项目文档', done: false }
]
}
})

⭐⭐ 使用示例

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
<template>
<div>
<h2>计数器{{ count }}</h2>
<p>用户名称{{ user.name }}</p>
<ul>
<li v-for="todo in todos" :key="todo.id">
{{ todo.text }} - {{ todo.done ? '已完成' : '未完成' }}
</li>
</ul>
</div>
</template>

<script>
export default {
computed: {
count() {
return this.$store.state.count
},
user() {
return this.$store.state.user
},
todos() {
return this.$store.state.todos
}
}
}
</script>

Getter

在 Vuex 中Getter 类似于 Vue 组件中的计算属性它用于从 state 中派生出一些状态这些状态可能需要基于现有的 state 进行一些转换或过滤Getter 的返回值会根据它的依赖被缓存起来并且只有当它的依赖值发生了变化时才会重新计算

缓存Getter 会基于它们的依赖进行缓存只有当依赖的状态发生变化时才会重新计算
接收其他 getter 作为第二个参数这意味着你可以将 getter 链接起来一个 getter 可以使用另一个 getter 的结果
访问方式可以通过 store.getters 访问到所有的 getter 函数也可以通过组件中的 this.$store.getters 来访问

⭐⭐ 定义数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
export default new Vuex.Store({
// 定义 state 对象
state: {
todos: [
{ id: 1, text: '学习 Vuex', done: true },
{ id: 2, text: '编写 Vuex 应用', done: false }
]
},
// 定义 getters 对象
getters: {
// 获取所有已完成的 todo 项
doneTodos: state => {
return state.todos.filter(todo => todo.done);
},
// 获取已完成 todo 项的数量
doneTodosCount: (state, getters) => {
return getters.doneTodos.length;
},
// 获取特定 ID 的 todo 项
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id);
}
}
});

⭐⭐ 使用示例

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
<template>
<div>
<p>{{ doneTodos }}</p>
<p>{{ doneTodosCount }}</p>
<p>{{ getTodoById(2) }}</p>
</div>
</template>

<script>
export default {
computed: {
// 使用 Vuex 的 mapGetters 辅助函数或者直接访问 this.$store.getters
doneTodos() {
// 返回 store 中已完成的 todos
return this.$store.getters.doneTodos;
},
doneTodosCount() {
// 返回已完成 todos 的数量
return this.$store.getters.doneTodosCount;
},
getTodoById() {
return id => this.$store.getters.getTodoById(id)
}
}
}
</script>

Mutation

在 Vuex 中Mutation 是唯一可以修改 state状态 的地方它是一个同步函数接收 state 作为第一个参数并通过更改 state 来改变应用的状态

特性 描述
同步操作 Mutation 必须是同步的这样可以确保状态变化可追踪易于调试
提交方式 使用 store.commit('mutationName', payload) 提交 mutation
命名规范 推荐使用常量命名如 INCREMENT便于维护和查找
不能异步 如果需要执行异步操作请使用 Action而不是直接在 Mutation 中写异步代码

⭐⭐ 定义数据

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
export default new Vuex.Store({
state: {
count: 0,
todos: [
{ id: 1, text: '学习 Vuex', done: true },
{ id: 2, text: '编写 Vuex 应用', done: false }
]
},
mutations: {
// 同步增加计数器
INCREMENT(state) {
state.count++
},

// 带参数的 mutation
INCREMENT_BY(state, payload) {
state.count += payload.amount
},

// 切换某个 todo 的完成状态
TOGGLE_TODO_DONE(state, id) {
const todo = state.todos.find(todo => todo.id === id)
if (todo) {
todo.done = !todo.done
}
}
}
})

⭐⭐ 使用示例

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
<template>
<div>
<p>当前计数{{ count }}</p>
<button @click="increment">+1</button>
<button @click="incrementBy(5)">+5</button>
<ul>
<li v-for="todo in todos" :key="todo.id">
{{ todo.text }} - {{ todo.done ? '已完成' : '未完成' }}
</li>
</ul>
<button @click="toggleTodoDone(1)">切换 ID=1 的任务状态</button>
</div>
</template>

<script>
export default {
computed: {
count() {
return this.$store.state.count
},
todos() {
return this.$store.state.todos
}
},
methods: {
increment() {
this.$store.commit('INCREMENT') // 不带参数
},
incrementBy(amount) {
this.$store.commit('INCREMENT_BY', { amount }) // 带参数
},
toggleTodoDone(id) {
this.$store.commit('TOGGLE_TODO_DONE', id) // 带参数
}
}
}
</script>

Action

在 Vuex 中Action 类似于 Mutation不同之处在于Action 提交的是 Mutation而不是直接变更状态Action 可以包含任意异步操作而 Mutation 必须是同步的通过 Action可以处理复杂的逻辑比如异步请求条件判断等并最终通过提交 Mutation 来改变状态

⭐⭐ 定义数据

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
export default new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++
},
decrement(state) {
state.count--
}
},
actions: {
// 异步增加
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
},

// 带参数的异步减少
decrementBy({ commit }, amount) {
setTimeout(() => {
commit('decrement')
if (amount > 1) {
console.log(`已减少 ${amount} 次`)
}
}, 1000)
}
}
})

⭐⭐ 使用示例

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
<template>
<div>
<h2>当前计数{{ count }}</h2>

<!-- 同步操作 -->
<button @click="increment">同步 +1</button>

<!-- 异步操作 -->
<button @click="incrementAsync">异步 +1</button>
<button @click="decrementBy(1)">异步 -1</button>
</div>
</template>

<script>
export default {
computed: {
count() {
return this.$store.state.count
}
},
methods: {
// 同步修改状态直接提交 mutation
increment() {
this.$store.commit('increment')
},

// 异步修改状态分发 action
incrementAsync() {
this.$store.dispatch('incrementAsync') // 不带参数
},
decrementBy(amount) {
this.$store.dispatch('decrementBy', amount) // 带参数
}
}
}
</script>

Modules

Vuex 的模块化Modules功能允许我们将 store 分割成模块module每个模块拥有自己的 statemutationaction 和 getter甚至还可以嵌套子模块这对于大型应用特别有用因为它帮助我们更好地组织和管理状态

独立的状态空间每个模块都有自己的状态
命名空间默认情况下模块内的 actionmutation 和 getter 是注册在全局命名空间下的这意味着如果你有两个不同模块定义了相同的 mutation 类型它们会冲突为了解决这个问题可以开启命名空间
嵌套模块模块可以嵌套形成更复杂的结构

⭐⭐ 定义数据

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
// store.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const userModule = {
namespaced: true, // 开启命名空间
state: {
username: '张三',
age: 25
},
mutations: {
setUsername(state, newUsername) {
state.username = newUsername
}
},
actions: {
updateUsername({ commit }, newUsername) {
setTimeout(() => {
commit('setUsername', newUsername)
}, 1000)
}
},
getters: {
userInfo: (state) => {
return `${state.username} - ${state.age} 岁`
}
}
}

const cartModule = {
namespaced: true, // 开启命名空间
state: {
items: [
{ id: 1, name: '商品A', price: 99 },
{ id: 2, name: '商品B', price: 199 }
]
},
mutations: {
removeItem(state, id) {
state.items = state.items.filter(item => item.id !== id)
}
}
}

export default new Vuex.Store({
modules: {
user: userModule,
cart: cartModule
}
})

⭐⭐ 使用示例

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
<template>
<div>
<h2>用户信息</h2>
<p>{{ userInfo }}</p>
<input v-model="newUsername" placeholder="输入新用户名">
<button @click="updateUsername">更新用户名异步</button>

<h2>购物车内容</h2>
<ul>
<li v-for="item in cartItems" :key="item.id">
{{ item.name }} - ¥{{ item.price }}
<button @click="removeItem(item.id)">移除</button>
</li>
</ul>
</div>
</template>

<script>
export default {
data() {
return {
newUsername: ''
}
},
computed: {
// 手动获取模块 user 中的 getter: userInfo
userInfo() {
return this.$store.getters['user/userInfo']
},
// 手动获取模块 cart 中的 state: items
cartItems() {
return this.$store.state.cart.items
}
},
methods: {
// 分发模块 user 中的 action: updateUsername
updateUsername() {
if (this.newUsername.trim()) {
this.$store.dispatch('user/updateUsername', this.newUsername)
}
},

// 提交模块 cart 中的 mutation: removeItem
removeItem(id) {
this.$store.commit('cart/removeItem', id)
}
}
}
</script>

辅助函数

mapState

mapState 是 Vuex 中的一个辅助函数用于帮助我们更方便地在计算属性中使用 state当一个组件需要获取多个状态时如果每次都从 this.$store.state 中读取会显得非常繁琐这时可以利用 mapState 辅助函数生成计算属性简化代码

⭐⭐ 定义数据

1
2
3
4
5
6
Vuex.Store({
state: {
count: 0,
message: 'Hello Vuex!'
}
});

⭐⭐ 使用函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div id="app">
<!-- 展示从store获取的状态 -->
<p>Count is: {{ count }}</p>
<p>Message is: {{ message }}</p>
</div>
</template>

<script>
import { mapState } from 'vuex';
export default {
computed: {
// 使用对象展开运算符将此对象混入到外部对象中
...mapState([
// 映射 this.count 为 store.state.count
'count',
// 映射 this.message 为 store.state.message
'message'
])
}
}
</script>

mapGetters

mapGetters 是 Vuex 提供的一个 辅助函数用于将 store 中的 getters 映射为组件的计算属性computed properties从而在模板中更方便地使用这些值

⭐⭐ 定义数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
export default new Vuex.Store({
state: {
todos: [
{ id: 1, text: 'Learn Vue', done: true },
{ id: 2, text: 'Build an app', done: false },
{ id: 3, text: 'Deploy it', done: true }
]
},
getters: {
// 获取所有已完成的待办事项
doneTodos(state) {
return state.todos.filter(todo => todo.done);
},
// 获取所有未完成的待办事项的数量
undoneTodosCount(state) {
return state.todos.filter(todo => !todo.done).length;
}
}
});

⭐⭐ 使用示例

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
<template>
<div>
<h2>Done Todos:</h2>
<ul>
<li v-for="todo in doneTodos" :key="todo.id">{{ todo.text }}</li>
</ul>

<p>Undone Todos Count: {{ undoneTodosCount }}</p>
</div>
</template>

<script>
import { mapGetters } from 'vuex';

export default {
computed: {
// 使用数组方式映射 getters要求计算属性名与 getter 名相同
...mapGetters(['doneTodos', 'undoneTodosCount'])
// 或使用对象形式重命名方法名
...mapGetters({
done: 'doneTodos',
totalUndone: 'undoneTodosCount'
})
}
};
</script>

mapMutations

mapMutations 是 Vuex 提供的一个 辅助函数用于将 store 中的 mutations 映射为组件中的方法methods从而在组件中更方便地调用这些 mutation 来修改 state

⭐⭐ 定义数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export default new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++;
},
decrement(state) {
state.count--;
},
// 接收参数的 mutation
addCount(state, payload) {
state.count += payload.amount;
}
}
});

⭐⭐ 使用示例

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
<template>
<div>
<p>当前计数{{ count }}</p>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
<button @click="addCount({ amount: 5 })">+5</button>
</div>
</template>

<script>
import { mapMutations } from 'vuex';

export default {
methods: {
// 使用数组方式映射 mutations要求方法名与 mutation 名相同
...mapMutations(['increment', 'decrement', 'addCount'])
// 或使用对象形式重命名方法名
...mapMutations({
increase: 'increment',
decrease: 'decrement',
add: 'addCount'
})
}
};
</script>

mapActions

mapActions 是 Vuex 提供的一个 辅助函数用于将 store 中的 actions 映射为组件中的方法methods从而在组件中更方便地调用这些 actions 来执行异步操作或提交 mutations

⭐⭐ 定义数据

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
export default new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++;
}
},
actions: {
// 简单的异步 action
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment');
}, 1000);
},

// 接收参数的 action
addCount(context, payload) {
setTimeout(() => {
context.commit('increment');
console.log('Added:', payload.amount);
}, 500);
}
}
});

⭐⭐ 使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<div>
<p>当前计数{{ count }}</p>
<button @click="incrementAsync">+1 (异步)</button>
<button @click="addCount({ amount: 5 })">+5</button>
</div>
</template>

<script>
import { mapActions } from 'vuex';

export default {
methods: {
// 使用数组方式映射 actions要求方法名与 action 名相同
...mapActions(['incrementAsync', 'addCount'])
// 或使用对象形式重命名方法名
...mapActions({
increase: 'incrementAsync',
addFive: 'addCount'
})
}
};
</script>

模块化使用

当我们使用模块化时为了更方便地访问模块中的 stategettersmutations 和 actions我们需要配合使用 mapStatemapGettersmapMutations 和 mapActions并指定模块路径

⭐⭐ 定义数据

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
// store/modules/userModule.js
export default {
namespaced: true, // 必须设置为 true 才能使用命名空间
state: {
username: '张三',
age: 25
},
getters: {
userInfo(state) {
return `姓名${state.username}年龄${state.age}`;
}
},
mutations: {
setUsername(state, newUsername) {
state.username = newUsername;
},
setAge(state, newAge) {
state.age = newAge;
}
},
actions: {
updateUserInfo({ commit }, payload) {
commit('setUsername', payload.username);
commit('setAge', payload.age);
}
}
};

// store/index.js
import Vue from 'vue';
import Vuex from 'vuex';

import userModule from './modules/userModule';

Vue.use(Vuex);

export default new Vuex.Store({
modules: {
user: userModule // 模块名是 user
}
});

⭐⭐ 使用示例

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
<template>
<div>
<h2>用户信息</h2>
<p>用户名{{ username }}</p>
<p>年龄{{ age }}</p>
<p>用户详情{{ userInfo }}</p>

<button @click="setUsername('李四')">修改用户名</button>
<button @click="setAge(30)">修改年龄</button>
<button @click="updateUserInfo({ username: '王五', age: 40 })">更新用户信息</button>
</div>
</template>

<script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex';

export default {
computed: {
...mapState('user', ['username', 'age']),
...mapGetters('user', ['userInfo'])
},
methods: {
...mapMutations('user', ['setUsername', 'setAge']),
...mapActions('user', ['updateUserInfo'])
}
};
</script>

Vue 3

Vue 3 相对于 Vue 2 有许多重要的改进和新特性


Composition API: Vue 3 引入了 Composition API允许更灵活地组织组件逻辑并解决了 Options API 中逻辑复用的问题
性能提升: Vue 3 的渲染性能提高了约 40%并且在初始加载时间和更新性能上都有显著的提升
更好的 TypeScript 支持: Vue 3 对 TypeScript 的集成更加紧密提供了更好的类型推导和错误检查
更小的打包体积: Vue 3 使用 Tree Shaking 技术和更高效的代码分割使得最终打包文件体积更小
响应式系统: Vue 3 使用 Proxy 替代 Vue 2 的 Object.defineProperty 来实现响应式系统这使得它能够侦测到更多类型的变更并且性能更好
Fragment 支持: Vue 3 允许组件返回多个根元素即支持 Fragment而 Vue 2 需要一个单一的根元素
v-model 语法变化: Vue 3 增强了 v-model 的工作机制支持自定义绑定的 prop 和事件并且可以同时使用多个 v-model 绑定
新的生命周期钩子: Vue 3 提供了一些新的生命周期钩子函数如 onMountedonUpdated 和 onUnmounted这些是为 Composition API 设计的
Teleport 和 Suspense: Vue 3 引入了 Teleport 组件用于跨 DOM 边界渲染内容以及 Suspense 用于异步依赖的占位加载
Router 和 Vuex 更新: Vue Router 和 Vuex 都有更新版本来更好地与 Vue 3 结合使用提供了基于 Composition API 的新功能
其他改进: Vue 3 支持自定义渲染器提供对 SSR 更好的支持

初始化项目

创建命令

1
2
3
4
5
6
7
8
9
10
11
# 需要Node 16.X 以上版本
node -v

# 创建 Vue 应用
npm init vue@latest

# 初始化项目进入项目执行该命令
npm install

# 运行项目
npm run dev

目录介绍

1
2
3
4
5
6
7
8
9
10
11
12
my-vue-app/
├── public/ # 静态资源目录不会经过 webpack/vite 处理
│ └── favicon.ico # 网站图标
├── src/
│ ├── assets/ # 静态资源如图片字体等会被 vite 构建处理
│ ├── components/ # 可复用的 Vue 组件
│ ├── App.vue # 根组件整个应用的入口
│ └── main.js # 应用入口文件创建 Vue 实例并挂载 App.vue
├── index.html # Vite 默认入口 HTML 文件自动注入打包后的 JS
├── package.json # 项目配置和依赖信息
├── vite.config.js # Vite 构建工具的核心配置文件
└── vitest.config.js # Vitest 测试框架的配置文件如果启用了测试功能

组合式 API

Vue 3 引入了组合式 APIComposition API这是一个基于函数的 API用于在组件内部逻辑复用和代码组织它旨在解决 Vue 2 中选项型 APIOptions API在处理复杂组件时的一些局限性

setup

在 Vue 3 中setup 函数是一个新的组件选项它作为组合式 APIComposition API的入口点会在组件实例创建之前执行这意味着你无法在这个阶段访问到 this因为此时组件实例尚未创建

⭐⭐ 使用示例

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
<template>
<div>
<p>{{ count }}</p>
<button @click="increment">Increment</button>
</div>
</template>

<script>
import { ref, onMounted } from 'vue';

export default {
setup() {
// 创建一个响应式引用
const count = ref(0);

// 定义方法
const increment = () => {
count.value++;
};

// 生命周期钩子
onMounted(() => {
console.log('Component is mounted!');
});

// 返回需要暴露给模板的数据和方法
return {
count,
increment
};
}
};
</script>

⭐⭐ 语法糖

使用 <script setup> 语法可以极大地简化组合式 API 的写法它是 Vue 3.2 引入的一种编译时语法糖使得代码更简洁直观特别适合使用组合式 API 的场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
</div>
</template>

<script setup>
import { ref } from 'vue'

const count = ref(0)

function increment() {
count.value++
}
</script>

reactive

在 Vue 3 的组合式 API 中reactive 是一个非常重要的函数用于创建响应式对象它通过 Proxy或在不支持 Proxy 的环境中使用 Object.defineProperty来追踪对象的属性变化并在数据变化时自动更新视图

⭐⭐ 使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<div>
<p>姓名: {{ user.name }}</p>
<p>年龄: {{ user.age }}</p>
<button @click="increaseAge">增加年龄</button>
</div>
</template>

<script setup>
import { reactive } from 'vue'

// 使用 reactive 创建响应式对象
const user = reactive({
name: '张三',
age: 25
})

function increaseAge() {
user.age++ // 修改响应式对象中的属性会触发视图更新
}
</script>

⭐⭐ 深层响应式对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { reactive } from 'vue'

const state = reactive({
user: {
name: '李四',
info: {
age: 30,
job: '工程师'
}
}
})

// 修改嵌套属性也会触发响应式更新
state.user.info.job = '前端开发'

ref

在 Vue 3 的组合式 API 中ref 是一个非常基础且常用的功能用于创建响应式的引用reference它特别适用于管理基本类型如数字字符串布尔值等也可以用于包装对象

⭐⭐ ref 与 reactive 对比

特性 ref reactive
数据类型 任意类型包括基本类型 对象或数组
是否需要 .value 需要 不需要
是否可重新赋值 重新赋值会丢失响应性
解构后是否保持响应性 需配合 toRefs

⭐⭐ 使用示例

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
<template>
<div>
<p>计数器: {{ count }}</p>
<button @click="increment">加一</button>
</div>
</template>

<script setup>
import { ref } from 'vue'

// 创建一个响应式的基本类型变量
const count = ref(0)

function increment() {
count.value++ // 注意要使用 .value 来修改值
}


// 创建一个响应式对象
const user = ref({
name: '张三',
age: 25
})

user.value.age = 26 // 必须通过 .value 修改整个对象
</script>

计算/监听

computed

computed 用于创建计算属性它基于其依赖的响应式数据进行缓存并仅在其依赖项发生变化时才重新计算从而提高性能

⭐⭐ 只读计算属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div>
<p>姓氏: {{ firstName }}</p>
<p>名字: {{ lastName }}</p>
<p>全名: {{ fullName }}</p>
</div>
</template>

<script setup>
import { ref, computed } from 'vue'

const firstName = ref('张')
const lastName = ref('三')

// 使用 computed 创建全名计算属性
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`
})
</script>

⭐⭐ 可写的计算属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<script setup>
import { ref, computed } from 'vue'

const firstName = ref('')
const lastName = ref('')

const fullName = computed({
get() {
return `${firstName.value} ${lastName.value}`.trim()
},
set(newValue) {
const names = newValue.split(' ')
firstName.value = names[0] || ''
lastName.value = names[1] || ''
}
})
</script>

<template>
<input v-model="fullName" placeholder="输入全名">
<p>姓氏: {{ firstName }}</p>
<p>名字: {{ lastName }}</p>
</template>

watch

watch 用于监听响应式数据的变化并在数据变化时执行副作用逻辑它与 Vue 2 中的选项 API 中的 watch 功能类似但在组合式 API 中提供了更灵活和强大的功能

⭐⭐ 监听单个属性

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

const count = ref(0)

// 监听 count 的变化
watch(count, (newVal, oldVal) => {
console.log(`count changed from ${oldVal} to ${newVal}`)
})

function increment() {
count.value++
}
</script>

<template>
<button @click="increment">Increment</button>
</template>

⭐⭐ 监听多个来源

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

const firstName = ref('张')
const lastName = ref('三')

watch([firstName, lastName], ([newFirstName, newLastName], [oldFirstName, oldLastName]) => {
console.log(`姓名从 "${oldFirstName} ${oldLastName}" 更改为 "${newFirstName} ${newLastName}"`)
})
</script>

<template>
<input v-model="firstName" placeholder="输入姓氏">
<input v-model="lastName" placeholder="输入名字">
</template>

⭐⭐ 使用 getter 函数监听

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

const firstName = ref('李')
const lastName = ref('四')

watch(() => `${firstName.value} ${lastName.value}`, (newName, oldName) => {
console.log(`姓名从 "${oldName}" 更改为 "${newName}"`)
})
</script>

<template>
<input v-model="firstName" placeholder="输入姓氏">
<input v-model="lastName" placeholder="输入名字">
</template>

⭐⭐ 立即执行监听器

1
2
3
4
5
6
7
8
9
10
11
12
13
<script setup>
import { ref, watch } from 'vue'

const message = ref('Hello World')

watch(message, (newVal, oldVal) => {
console.log(`message changed from ${oldVal} to ${newVal}`)
}, { immediate: true })
</script>

<template>
<input v-model="message" placeholder="修改消息">
</template>

⭐⭐ 监听深层对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script setup>
import { reactive, watch } from 'vue'

const user = reactive({
profile: {
name: '王五',
age: 28,
address: {
city: '北京'
}
}
})

watch(() => user.profile, (newVal, oldVal) => {
console.log('profile changed:', newVal, oldVal)
}, { deep: true })
</script>

<template>
<input v-model="user.profile.name" placeholder="输入姓名">
<input v-model="user.profile.address.city" placeholder="输入城市">
</template>

生命周期函数

生命周期钩子函数以函数形式提供不再使用选项式 API 中的 mountedcreated 等写法这些钩子函数必须在组件的 setup() 函数或 <script setup> 中调用

函数概述

钩子函数 触发时机
onBeforeMount 组件挂载之前
onMounted 组件挂载完成后
onBeforeUpdate 数据更新前模板尚未重新渲染
onUpdated 数据更新后模板已重新渲染
onBeforeUnmount 组件卸载前
onUnmounted 组件卸载后
onActivated 缓存的组件激活时调用
onDeactivated 缓存的组件停用时调用
onErrorCaptured 捕获子孙组件错误

使用示例

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
<script setup>
import {
onBeforeMount, onMounted,
onBeforeUpdate, onUpdated,
onBeforeUnmount, onUnmounted,
onActivated, onDeactivated,
onErrorCaptured
} from 'vue'

onBeforeMount(() => {
console.log('组件即将挂载')
})
onMounted(() => {
console.log('组件已挂载')
// 可操作 DOM
})
onBeforeUpdate(() => {
console.log('数据已更新DOM 尚未更新')
})
onUpdated(() => {
console.log('DOM 已更新')
})
onBeforeUnmount(() => {
console.log('组件即将卸载清理定时器')
})
onUnmounted(() => {
console.log('组件已卸载')
})
onActivated(() => {
console.log('组件被激活')
})
onDeactivated(() => {
console.log('组件被停用')
})
onErrorCaptured((err, instance, info) => {
console.error('捕获到错误:', err, info)
// 返回 false 可阻止继续向上传播
})
</script>

组件通信

父传子

props 是父子组件之间传递数据的标准方式

⭐⭐ 父组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- ParentComponent.vue -->
<template>
<div>
<h2>父组件</h2>
<ChildComponent :message="parentMessage" :count="parentCount" />
</div>
</template>

<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'

const parentMessage = ref('你好子组件')
const parentCount = ref(10)
</script>

⭐⭐ 子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- ChildComponent.vue -->
<template>
<div>
<p>来自父组件的消息: {{ message }}</p>
<p>来自父组件的数字: {{ count }}</p>
</div>
</template>

<script setup>
import { defineProps } from 'vue'

// 接收 props
const props = defineProps({
message: {
type: String,
required: true
},
count: {
type: Number,
default: 0
}
})
</script>

子传父

子组件通过 $emit 触发自定义事件父组件监听该事件并接收数据

⭐⭐ 子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- ChildComponent.vue -->
<template>
<button @click="sendToParent">点击发送消息给父组件</button>
</template>

<script setup>
import { defineEmits } from 'vue'

const emit = defineEmits(['update'])

function sendToParent() {
emit('update', '这是来自子组件的消息')
}
</script>

⭐⭐ 父组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- ParentComponent.vue -->
<template>
<div>
<h2>父组件</h2>
<ChildComponent @update="handleUpdate" />
<p>接收到子组件的消息{{ childMessage }}</p>
</div>
</template>

<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'

const childMessage = ref('')

function handleUpdate(msg) {
childMessage.value = msg
}
</script>

跨层级通信

provideinject 用于跨越多个组件层级传递数据适合全局状态共享如主题用户信息等

⭐⭐ 根组件

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div id="app">
<LevelOne />
</div>
</template>

<script setup>
import { provide, ref } from 'vue'
import LevelOne from './LevelOne.vue'

const theme = ref('dark')
provide('theme', theme)
</script>

⭐⭐ 一层组件

1
2
3
4
5
6
7
8
<!-- LevelOne.vue -->
<template>
<LevelTwo />
</template>

<script setup>
import LevelTwo from './LevelTwo.vue'
</script>

⭐⭐ 二层组件

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- LevelTwo.vue -->
<template>
<div :class="themeClass">
当前主题: {{ theme }}
</div>
</template>

<script setup>
import { inject } from 'vue'

const theme = inject('theme')
const themeClass = computed(() => `theme-${theme.value}`)
</script>

模版引用

模板引用Template Refs提供了一种直接访问 DOM 元素或子组件实例的方法通过 ref 属性标记目标元素或组件并在组合式 API 中使用 ref 函数来声明对应的引用变量

引用DOM元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<button @click="focusInput">聚焦输入框</button>
<!-- 使用 ref 标记输入框 -->
<input ref="inputField" placeholder="点击按钮聚焦我">
</template>

<script setup>
import { ref } from 'vue'

// 创建引用
const inputField = ref(null)

function focusInput() {
// 确保 inputField 已经被挂载
if (inputField.value) {
inputField.value.focus()
}
}
</script>

引用子组件

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
<!-- ChildComponent.vue -->
<template>
<p>{{ message }}</p>
</template>

<script setup>
import { ref } from 'vue'

const message = ref('Hello from Child Component')

// 如果子组件是用 <script setup> 编写的则不能直接访问其内部的响应式数据
// 明确暴露给父组件的数据
defineExpose({
message
})
</script>

<!-- ParentComponent.vue -->
<template>
<ChildComponent ref="childComponent" />
<button @click="logMessage">查看子组件消息</button>
</template>

<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'

const childComponent = ref(null)

function logMessage() {
console.log(childComponent.value.message)
}
</script>

结合生命周期函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div ref="contentBox">内容盒子</div>
</template>

<script setup>
import { ref, onMounted } from 'vue'

const contentBox = ref(null)

onMounted(() => {
if (contentBox.value) {
console.log('Content box 宽度:', contentBox.value.offsetWidth)
}
})
</script>

编译器宏

Vue 3 的 组合式 APIComposition API尤其是在使用 <script setup> 语法时我们可以通过一些内置的编译器宏来定义组件的行为比如 propsemitsexpose 等

defineProps

用于声明组件接收的 props

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script setup>
const props = defineProps({
title: String,
count: {
type: Number,
default: 0
}
})
</script>

<template>
<h1>{{ title }}</h1>
<p>Count: {{ count }}</p>
</template>

defineEmits

用于声明组件会触发的事件

1
2
3
4
5
6
7
8
9
10
11
<script setup>
const emit = defineEmits(['update:title', 'submit'])

function onSubmit() {
emit('submit', { data: 'Form submitted' })
}
</script>

<template>
<button @click="onSubmit">Submit</button>
</template>

defineExpose

暴露组件内部方法或变量供父组件通过 ref 调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- ChildComponent.vue -->
<script setup>
function sayHello() {
console.log('Hello from child')
}

defineExpose({ sayHello })
</script>

<!-- ParentComponent.vue -->
<template>
<ChildComponent ref="childRef" />
<button @click="callChildMethod">Call Child Method</button>
</template>

<script setup>
import { ref } from 'vue'
const childRef = ref()

function callChildMethod() {
childRef.value?.sayHello()
}
</script>

defineOptions

用于设置组件的配置选项如 namepropsemits 等如果使用了 defineOptions 来声明 props 或 emits就不能再使用 defineProps 和 defineEmits

1
2
3
4
5
6
7
<script setup>
defineOptions({
name: 'MyComponent',
props: ['title'],
emits: ['submit']
})
</script>

defineModel

简化 v-model 的双向绑定定义Vue 3.4+ 引入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- ChildComponent.vue -->
<script setup>
const firstName = defineModel('firstName')
const lastName = defineModel('lastName')
</script>

<template>
<input v-model="firstName">
<input v-model="lastName">
</template>

<!-- ParentComponent.vue -->
<template>
<ChildComponent v-model:firstName="first" v-model:lastName="last" />
</template>

withDefaults

为 defineProps 提供默认值

1
2
3
4
5
6
7
8
9
10
11
<script setup>
const props = withDefaults(
defineProps<{
title: string
count?: number
}>(),
{
count: 0
}
)
</script>

defineSlots

获取插槽信息不推荐手动使用一般通过 <slot> 使用即可

1
2
3
4
<script setup>
const slots = defineSlots()
console.log(slots.default ? 'Default slot exists' : 'No default slot')
</script>

路由管理

安装路由

1
npm install vue-router@4

路由配置

⭐⭐ 创建路由

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
// 创建一个 router/index.js 文件来配置路由
// 引入 Vue 和 VueRouter
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue' // 假设你有一个首页组件
import About from '../views/About.vue' // 假设你有一个关于页面组件

// 创建路由实例
const router = createRouter({
history: createWebHistory(), // 使用 HTML5 History 模式
routes: [
{
path: '/', // 主页路径
name: 'Home', // 路由名称
component: Home // 组件对应
},
{
path: '/about', // 关于页面路径
name: 'About',
component: About
}
]
})

export default router

⭐⭐ 注册路由

1
2
3
4
5
6
7
import { createApp } from 'vue'
import App from './App.vue'
import router from './router' // 引入之前创建的路由配置

const app = createApp(App)
app.use(router) // 将路由注册到 Vue 应用中
app.mount('#app')

⭐⭐ App.vue

1
2
3
4
5
6
7
8
9
10
11
<template>
<div id="app">
<!-- 使用 router-link 组件进行导航 -->
<router-link to="/">首页</router-link>
<router-link to="/about">关于我们</router-link>

<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
</template>

路由模式

history 配置决定了路由如何管理 URL

⭐⭐ HTML5 History 模式

它使用浏览器的 history API 来实现 URL 导航而不重新加载页面适用于现代 Web 应用尤其是单页应用SPA

1
2
3
4
5
6
7
8
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
history: createWebHistory(),
routes: [
// 路由配置...
]
})

⭐⭐ Hash 模式

使用 URL 的 hash 部分即 # 后面的部分来模拟一个完整的 URL以便在不支持 HTML5 History API 的浏览器中进行兼容

1
2
3
4
5
6
7
8
import { createRouter, createWebHashHistory } from 'vue-router'

const router = createRouter({
history: createWebHashHistory(),
routes: [
// 路由配置...
]
})

基路径配置

base 配置项定义了应用的基础路径如果你的应用被部署在一个子路径下则需要通过这个选项指定该子路径例如如果你的应用部署在 http://example.com/my-app/那么 base 应该设置为 /my-app/

⭐⭐ 直接配置

1
2
3
4
5
6
7
8
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
history: createWebHistory('/my-app/'), // 设置基础路径
routes: [
// 路由配置...
]
})

⭐⭐ 环境变量配置

1
2
3
4
5
6
7
// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
base: '/my-app/', // 设置基础路径
});
1
2
3
4
5
6
7
8
9
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
history: createWebHistory(import.meta.env.BASE_UR), // 设置基础路径
routes: [
// 路由配置...
]
})

导航守卫

⭐⭐ 全局前置守卫

这是最常用的全局守卫适用于整个应用的每个路由跳转可以在路由配置文件中通过 router.beforeEach 方法注册一个全局前置守卫

1
2
3
4
5
6
7
router.beforeEach((to, from) => {
// to: 即将要进入的目标路由
// from: 当前导航正要离开的路由
// 返回 false 取消导航
// 返回路由地址进行重定向: return '/login'
return true; // 导航有效
});

⭐⭐ 全局解析守卫

类似于 beforeEach但是它确保在导航被确认之前所有组件内守卫和异步路由组件都已经被解析

1
2
3
4
5
router.beforeResolve(async (to) => {
if (to.meta.requiresCamera) {
await askForCameraPermission();
}
});

⭐⭐ 全局后置钩子

这些钩子不会改变导航本身通常用于分析更改页面标题等辅助功能

1
2
3
router.afterEach((to, from) => {
document.title = to.meta.title || 'Default Title';
});

⭐⭐ 路由独享守卫

可以在路由配置中为特定路由定义 beforeEnter 守卫只对该路由生效

1
2
3
4
5
6
7
8
9
const routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: (to, from) => {
// 针对这个路由的逻辑
},
},
];

⭐⭐ 组件内守卫

这些守卫是定义在组件内部的允许你控制组件自身的进入和离开行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export default {
name: 'MyComponent',
beforeRouteEnter(to, from, next) {
// 进入组件前的操作
next();
},
beforeRouteUpdate(to, from, next) {
// 路由更新时的操作
next();
},
beforeRouteLeave(to, from, next) {
// 离开组件前的操作
next();
},
};

状态管理

Vue 3 推荐使用 Pinia 状态管理库它提供了一个更简单更直观的方式来管理应用的状态

基本使用

⭐⭐ 安装 pinia 组件库

1
npm install pinia

⭐⭐ 定义 Store

1
2
3
4
5
6
7
8
9
10
11
12
13
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const app = createApp(App)

// 创建 Pinia 实例
const pinia = createPinia()

// 使用 Pinia
app.use(pinia)

app.mount('#app')

⭐⭐ 定义 Store

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { defineStore } from 'pinia'

export const useMainStore = defineStore('main', {
// 状态(state)是响应式的
state: () => ({
count: 0,
name: 'Pinia'
}),
// getters 类似于计算属性可以接受其他 getters 作为参数
getters: {
doubleCount: (state) => state.count * 2,
fullName: (state) => `${state.name} Store`
},
// actions 用于更新状态
actions: {
increment() {
this.count++
},
updateName(newName) {
this.name = newName
}
}
})

⭐⭐ 定义 Store

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<div>
<p>{{ store.count }}</p>
<p>{{ store.doubleCount }}</p>
<button @click="store.increment">Increment</button>
</div>
</template>

<script>
import { useMainStore } from './stores/main'
import { onMounted } from 'vue'

export default {
setup() {
const store = useMainStore()

onMounted(() => {
console.log(store.fullName)
})

return { store }
}
}
</script>

Store

Store 是 Pinia 中最基本的概念用来保存状态state计算属性getters和修改状态的方法actions天然模块化每个 store 是一个模块不需要手动配置命名空间

⭐⭐ 基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// stores/counterStore.js
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
}),
getters: {
doubleCount: (state) => state.count * 2,
},
actions: {
increment() {
this.count++
},
decrement() {
this.count--
}
}
})

⭐⭐ 组合式API写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// stores/counterStore.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useCounterStore = defineStore('counter', () => {
// 响应式状态
const count = ref(0)

// 计算属性双倍值
const doubleCount = computed(() => count.value * 2)

// 修改状态的方法
function increment() {
count.value++
}

function decrement() {
count.value--
}

// 暴露出去供外部访问
return { count, doubleCount, increment, decrement }
})

⭐⭐ 在组件中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div>
<p>当前计数{{ counter.count }}</p>
<p>双倍计数{{ counter.doubleCount }}</p>
<button @click="counter.increment()">+1</button>
<button @click="counter.decrement()">-1</button>
</div>
</template>

<script setup>
import { useCounterStore } from '@/stores/counterStore'
const counter = useCounterStore()
</script>

⭐⭐ 重置 Store

1
2
const userStore = useUserStore()
userStore.$reset()

⭐⭐ 订阅状态变化

1
2
3
store.$subscribe((mutation, state) => {
console.log('状态发生了变化:', state)
})

统一导出

⭐⭐ 目录结构

1
2
3
4
5
6
7
8
src/
├── stores/
│ ├── index.js // 入口文件可选
│ ├── counterStore.js
│ ├── userStore.js
│ └── todoStore.js
├── views/
└── components/

⭐⭐ 统一导出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// stores/index.js

// 写法一
export * from './counterStore'
export * from './userStore'
export * from './todoStore'

// 或者
import { useCounterStore } from './counterStore'
import { useUserStore } from './userStore'

export default {
useCounterStore,
useUserStore
}

⭐⭐ 在组件中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
// 方法一通过具名导入使用
import { useCounterStore, useUserStore } from '@/stores'
// 或者
import * as stores from '@/stores'
const counter = stores.useCounterStore()
const user = stores.useUserStore()


// 方法二通过默认导出使用
import stores from '@/stores'

const counter = stores.useCounterStore()
const user = stores.useUserStore()

数据解构

在 Pinia 中解构 Store 的数据stategettersactions 是一个常见的操作尤其是在组件中使用时但如果不注意可能会导致 响应性丢失

⭐⭐ 安全解构

1
2
3
4
5
6
7
8
9
10
11
<script setup>
import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/stores/counterStore'

const counter = useCounterStore()

// 安全解构属性要保持响应性
const { count } = storeToRefs(counter)
// 函数不需要响应的特性可以直接解构
const { doubleCount, increment } = counter
</script>

⭐⭐ 直接调用

1
2
3
4
5
6
7
8
9
10
11
<script setup>
import { useCounterStore } from '@/stores/counterStore'

const counter = useCounterStore()
</script>

<template>
<p>当前计数{{ counter.count }}</p>
<p>双倍计数{{ counter.doubleCount }}</p>
<button @click="counter.increment()">+1</button>
</template>

⭐⭐ 组合式 API 写法中的解构

如果使用的是 Composition API 风格定义的 store即使用 ref, computed 等返回值可以直接解构因为这些值本身就是响应式的

1
2
3
4
5
6
7
8
9
10
11
// stores/counterStore.js
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)

function increment() {
count.value++
}

return { count, doubleCount, increment }
})
1
2
3
4
5
6
7
8
9
10
11
<script setup>
import { useCounterStore } from '@/stores/counterStore'

const { count, doubleCount, increment } = useCounterStore()
</script>

<template>
<p>{{ count }}</p>
<p>{{ doubleCount }}</p>
<button @click="increment">+1</button>
</template>

数据持久化

默认情况下刷新页面后状态会丢失我们可以使用 pinia-plugin-persistedstate 插件实现持久化

⭐⭐ 安装插件

1
npm install pinia-plugin-persistedstate

⭐⭐ 配置插件

1
2
3
4
5
6
7
8
9
10
11
// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)

const app = createApp(App)
app.use(pinia)
app.mount('#app')

⭐⭐ 在 Store 中启用持久化

1
2
3
4
5
6
7
export const useUserStore = defineStore('user', {
state: () => ({
username: '',
token: null
}),
persist: true, // 开启持久化
})

⭐⭐ 更换持久化储存仓库

1
2
3
4
5
6
7
8
9
export const useUserStore = defineStore('user', {
state: () => ({
username: '',
token: null
}),
persist: {
storage: localStorage, // 默认就是 localStorage
}
})

监听状态变化

Pinia 的状态本质上是响应式的因此可以像监听普通响应式数据一样监听它们

1
2
3
4
5
6
7
8
9
10
11
import { watch } from 'vue'
import { useCounterStore } from '@/stores/counterStore'

const counter = useCounterStore()

watch(
() => counter.count,
(newVal) => {
console.log('count 发生了变化:', newVal)
}
)

插件系统

Pinia 提供了插件机制允许你在创建 store 之前或之后执行一些操作

⭐⭐ 日志插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// plugins/loggerPlugin.js
export const loggerPlugin = (context) => {
console.log('Pinia 初始化插件...')

context.pinia.use(() => {
return ({ store }) => {
store.$onAction(({ name, store, args }) => {
console.log(`[ACTION] ${store.$id}.${name} 被调用参数:`, args)
})

store.$subscribe((mutation, state) => {
console.log(`[STATE CHANGE] ${mutation.storeId}`, state)
})
}
})
}

⭐⭐ 使用插件

1
2
3
4
5
6
7
8
// main.js
import { createPinia } from 'pinia'
import { loggerPlugin } from './plugins/loggerPlugin'

const pinia = createPinia()
pinia.use(loggerPlugin)

app.use(pinia)

组件库

Axios

Axios 是一个基于 Promise 的 HTTP 客户端用于浏览器和 Node.js它能够发出请求到 REST endpoints 并且可以与 Vue.js 等框架很好地集成

中文文档

地址http://www.axios-js.com/zh-cn/docs

安装组件

1
npm install axios

基本使用

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
<template>
<div>
<!-- 显示获取的数据 -->
<p>{{ info }}</p>
</div>
</template>

<script>
import axios from 'axios';

export default {
data() {
return {
info: null,
};
},
// 在组件创建时发起请求
created() {
axios
.get('https://api.example.com/data') // 替换为实际的 API URL
.then(response => (this.info = response.data)) // 成功时更新数据
.catch(error => console.error(error)); // 错误处理
}
};
</script>

创建实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import axios from 'axios';

const instance = axios.create({
baseURL: 'https://api.example.com',
timeout: 1000,
headers: {'X-Custom-Header': 'foobar'}
});

// 使用实例发起请求
instance.get('/data')
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.error(error);
});

export default instance

添加拦截器

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
import axios from 'axios';

const instance = axios.create({
baseURL: 'https://api.example.com',
timeout: 1000,
headers: {'X-Custom-Header': 'foobar'}
});

// 使用实例发起请求
instance.get('/data')
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.error(error);
});

// 添加请求拦截器
instance.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});

// 添加响应拦截器
instance.interceptors.response.use(function (response) {
// 对响应数据做点什么
return response;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});

export default instance

Vant-UI

Vant 是一个轻量可定制的移动端组件库

中文文档

地址https://vant-ui.github.io/vant/#/zh-CN

安装组件

1
2
3
4
# 安装 Vant
npm i vant@latest-v2
# 按需加载插件在vant@V2中有效
npm install babel-plugin-import --save-dev

配置插件

1
2
3
4
5
6
7
8
9
10
// babel.config.js
{
"plugins": [
["import", {
"libraryName": "vant",
"libraryDirectory": "es",
"style": true
}, "vant"]
]
}

全局引入

1
2
3
4
5
6
7
import Vant from 'vant';
import 'vant/lib/index.css';

const app = createApp(App);

app.use(Vant);
app.mount('#app');

使用组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<van-button type="primary">主要按钮</van-button>
<van-button type="info">信息按钮</van-button>
<van-button type="default">默认按钮</van-button>
<van-button type="warning">警告按钮</van-button>
<van-button type="danger">危险按钮</van-button>
</template>

<script>
// 引入按钮组件
import { Button } from 'vant';

export default {
name: 'App',
components: {
[Button.name]: Button, // 注册组件为 van-button
},
};
</script>

Element Plus

‌Element Plus 是一款基于 Vue 3 的 UI 组件库专为中后台管理系统设计提供丰富的组件响应式布局主题定制和国际化支持

中文文档

地址https://cn.element-plus.org/zh-CN/guide/design.html

安装组件

1
npm install element-plus --save

导入组件

⭐⭐ 完整引入

如果你对打包后的文件大小不是很在乎那么使用完整导入会更方便

1
2
3
4
5
6
7
8
9
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'

const app = createApp(App)

app.use(ElementPlus)
app.mount('#app')

⭐⭐ 按需自动导入

需要使用额外的插件来导入要使用的组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 第一步安装插件
// npm install -D unplugin-vue-components unplugin-auto-import

// 第二步修改配置文件
// vite.config.ts
export default defineConfig({
// ...

// 添加以下配置
plugins: [
// ...
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
})

// 第三步在组件中直接使用

⭐⭐ 按需手动导入

需要安装 unplugin-element-plus 插件来导入样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 第一步安装插件
// npm install -D unplugin-vue-components

// 第二步修改配置文件
// vite.config.ts
export default defineConfig({
// ...

// 添加以下配置
plugins: [ElementPlus()],
})

// 第三步在组件中导入使用
import { ElButton } from 'element-plus'

使用组件

1
2
3
4
<template>
<el-button type="primary">主要按钮</el-button>
<el-input v-model="input" placeholder="请输入内容"></el-input>
</template>

全局配置

在引入 ElementPlus 时可以传入一个包含 size 和 zIndex 属性的全局配置对象 size 用于设置表单组件的默认尺寸zIndex 用于设置弹出组件的层级zIndex 的默认值为 2000

1
2
3
4
5
6
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import App from './App.vue'

const app = createApp(App)
app.use(ElementPlus, { size: 'small', zIndex: 3000 })

构建工具

打包部署

⭐⭐ 配置

1
2
3
4
5
// vue.config.js
module.exports = {
publicPath: './', // 部署应用时的基本 URL默认 '/'
outputDir: 'dist', // 构建输出目录默认 'dist'
};

⭐⭐ 打包

1
npm run build

⭐⭐ 部署

将打包好的dist文件夹直接部署服务器即可

Webpack

Webpack 是一个强大的模块打包工具主要用于现代 JavaScript 应用程序它不仅可以处理 JavaScript 文件还能通过插件和加载器loaders来转换和管理其他类型的文件如 CSS图片等使用 Vue CLI 构建 VUE 项目Vue CLI 就是基于 Webpack 构建的它为 Vue.js 应用提供了一套开箱即用的标准构建流程其底层使用了 Webpack 来处理模块打包热更新代码分割等核心功能并封装了默认配置开发者无需手动编写 webpack.config.js 文件即可进行项目构建和开发

中文文档

地址https://www.webpackjs.com/concepts

安装 Webpack

1
2
3
4
5
# 始化一个新的 npm 项目
npm init -y

# 安装 Webpack
npm install --save-dev webpack webpack-cli

基本配置

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
// 创建一个 webpack.config.js 文件作为 Webpack 的配置文件
// webpack.config.js
const path = require('path');

module.exports = {
// 入口文件
entry: './src/index.js',
// 输出配置
output: {
filename: 'bundle.js', // 输出的文件名
path: path.resolve(__dirname, 'dist') // 输出的路径
},
module: {
rules: [
// 使用babel-loader来转译ES6+语法
{
test: /\.js$/, // 匹配.js结尾的文件
exclude: /node_modules/, // 排除node_modules目录
use: {
loader: 'babel-loader',
}
}
]
}
};

加载器

为了能够处理不同类型的文件我们需要使用相应的加载器比如处理CSS文件可以使用style-loader和css-loader

1
2
3
4
5
6
7
npm install --save-dev style-loader css-loader

# 然后在webpack.config.js中添加规则
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}

插件

插件用于执行更广泛的任务如优化资源管理和环境变量注入例如HtmlWebpackPlugin可以帮助我们自动创建HTML文件并将打包好的JS文件自动引入

1
2
3
4
5
6
7
8
9
10
11
12
npm install --save-dev html-webpack-plugin

# 然后在webpack.config.js中添加规则
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
// ... 省略其他配置
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html' // 指定模板文件
})
]
};

打包

1
2
3
4
5
6
// 在package.json中添加脚本来简化构建过程
"scripts": {
"build": "webpack"
}

// 通过运行npm run build来启动 Webpack 构建

Vite

Vue 3 开始Vue 官方推荐使用 Vite 作为默认开发工具Vite 是一个新型的前端构建工具它通过原生 ES 模块ESM提供闪电般的冷启动速度并支持多种框架如 VueReactPreact 等Vite 利用了现代浏览器对 ES 模块的原生支持避免了传统打包工具如 Webpack 或 Rollup需要进行的完整打包过程

中文文档

地址https://cn.vitejs.dev/guide

核心特性


极速冷启动无需打包编译直接通过 ESM 加载模块
即时热更新在开发模式下修改代码后可以实现毫秒级的更新
开箱即用内置 TypeScriptJSXCSS 预处理器等支持
插件系统可以通过插件扩展功能
生产优化在生产环境使用 Rollup 进行打包优化

插件系统

插件是 Vite 的核心扩展机制用于处理各种类型的文件优化依赖等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import react from '@vitejs/plugin-react'
import legacy from '@vitejs/plugin-legacy'

export default defineConfig({
plugins: [
vue(), // 支持 Vue 单文件组件
react(), // 支持 React JSX
legacy({ // 旧版浏览器支持
targets: { chrome: '64' },
additionalLegacyPolyfills: ['regenerator-runtime/runtime']
})
]
})

CSS 相关配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export default defineConfig({
css: {
modules: { // CSS Modules 配置
localsConvention: 'camelCaseOnly'
},
postcss: { // PostCSS 配置
plugins: {
autoprefixer: {}
}
},
preprocessorOptions: { // CSS 预处理器配置
scss: {
additionalData: `@import "./src/variables.scss";`
}
}
}
})

全局常量替换

1
2
3
4
5
export default defineConfig({
define: {
__APP_ENV__: JSON.stringify('production') // 定义全局变量
}
})

开发服务器配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
export default defineConfig({
server: {
port: 3000, // 设置开发服务器端口
open: true, // 自动打开浏览器
host: '0.0.0.0', // 允许外部访问
https: false, // 是否启用 HTTPS
proxy: { // 配置代理
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
},
hmr: { // 热更新配置
protocol: 'ws',
port: 24678
}
}
})

生产构建配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
export default defineConfig({
build: {
target: 'modules', // 构建目标默认为 'modules'
outDir: 'dist', // 输出目录
assetsDir: 'assets', // 静态资源存放目录
assetsInlineLimit: 4096, // 小于该值的资源会被内联为 base64
cssCodeSplit: true, // 启用 CSS 代码分割
sourcemap: false, // 不生成 source map
rollupOptions: { // Rollup 配置
input: {
main: path.resolve(__dirname, './index.html'),
about: path.resolve(__dirname, './about.html')
}
},
lib: { // 构建库模式
entry: './lib/main.js',
name: 'MyLib',
fileName: format => `my-lib.${format}.js`
}
}
})

模块解析配置

1
2
3
4
5
6
7
8
export default defineConfig({
resolve: {
alias: { // 路径别名
'@': path.resolve(__dirname, './src')
},
extensions: ['.js', '.vue', '.json', '.wasm'] // 解析扩展名
}
})

依赖预构建配置

1
2
3
4
5
6
export default defineConfig({
optimizeDeps: {
include: ['lodash-es', 'axios'], // 显式指定需要预构建的依赖
exclude: ['moment'] // 排除某些不需要预构建的依赖
}
})

日志级别配置

1
2
3
4
export default defineConfig({
logLevel: 'info', // 可选 'info', 'warn', 'error', 'silent'
clearScreen: false // 不清空控制台
})

环境变量前缀配置

1
2
3
export default defineConfig({
envPrefix: 'VITE_' // 只暴露以 VITE_ 开头的环境变量
})

公共基础路径配置

1
2
3
4
5
export default defineConfig({
base: '/my-app/', // 部署在子路径时使用
// 或者使用动态模式
base: process.env.VITE_MYAPP_BASE_PATH || '/'
})

VSCode插件

Vetur

Vetur 专为 Vue.js 开发者设计它提供了对 .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
// VS Code settings.json : 可选配置
{
// 启用实验性的 Vue 模板特性支持根据需要启用
"vetur.experimental.template": true,

// 是否使用工作区依赖通常不需要更改默认为 false
"vetur.useWorkspaceDependencies": false,

// 设置 HTML 格式化工具为 Prettier
"vetur.format.defaultFormatter.html": "prettier",

// 设置 JavaScript 格式化工具为 Prettier
"vetur.format.defaultFormatter.js": "prettier",

// 设置 CSS 格式化工具为 Prettier
"vetur.format.defaultFormatter.css": "prettier",

// 设置 SCSS 格式化工具为 Prettier
"vetur.format.defaultFormatter.scss": "prettier",

// 设置 LESS 格式化工具为 Prettier
"vetur.format.defaultFormatter.less": "prettier",

// 设置 PostCSS 格式化工具为 Prettier
"vetur.format.defaultFormatter.postcss": "prettier",

// 设置格式化选项这里设置不使用分号和使用单引号
"vetur.format.options": {
"semi": false, // 禁用分号
"singleQuote": true // 使用单引号
},

// 启用模板验证检查 <template> 中的错误
"vetur.validation.template": true,

// 启用脚本验证检查 <script> 中的错误
"vetur.validation.script": true,

// 启用样式验证检查 <style> 中的错误
"vetur.validation.style": true
}

Vue - Official

Vue - Official插件 是 Vue 官方团队为 Visual Studio Code 推出的新一代官方插件专为 Vue 3特别是使用 TypeScript 和组合式 API开发提供全面支持这个插件是对旧版 Vetur 的替代方案旨在解决 Vetur 在 Vue 3 + TypeScript 支持上的局限性

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
// VS Code settings.json
// 为了防止冲突建议禁用或卸载 Vetur
{
"vetur.enabled": false
}

// Vue - Official : 可选配置
{
// 启用 Vue 官方语言服务
"vue.enable.officialPlugin": true,

// 设置默认格式化工具为 prettier可选
"vue.format.defaultFormatter.html": "prettier",
"vue.format.defaultFormatter.css": "prettier",
"vue.format.defaultFormatter.js": "prettier",
"vue.format.defaultFormatter.ts": "prettier",

// 自动修复保存时的 lint 错误需配合 ESLint
"editor.codeActionsOnSave": {
"source.fixAll": true,
"source.fixAll.eslint": true
},
"editor.formatOnSave": false,

// 开启模板验证
"vue.validate.template": true,
"vue.validate.script": true,
"vue.validate.style": true
}

ESLint

ESLint 是一个 JavaScript/TypeScript 代码检查工具广泛用于 VS Code 中帮助开发者遵循最佳实践保持代码风格一致并避免常见错误

1
2
3
4
5
6
7
8
9
// VS Code settings.json : 可选配置
{
"editor.codeActionsOnSave": {
"source.fixAll": true, // 保存文件时自动修复代码
"source.fixAll.eslint": true, // 明确只对 ESLint 执行 fixAll
"source.organizeImports": true // 自动整理导入语句如 TypeScript
},
"editor.formatOnSave": false // 禁用 VS Code 内置的保存时自动格式化功能
}

学习资源

  • 视频
    • Vue2+Vue3基础入门到实战项目全套教程https://www.bilibili.com/video/BV1HV4y1a7n4
  • 文档
    • 菜鸟教程 VUE2https://www.runoob.com/vue2/vue-tutorial.html
    • 菜鸟教程 VUE3https://www.runoob.com/vue3/vue3-tutorial.html
    • 官方文档 VUE2https://v2.cn.vuejs.org/v2/guide
    • 官方文档 VUE3https://cn.vuejs.org/guide/introduction.html
    • 官方文档 路由https://v3.router.vuejs.org/zh