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 可以很好地与其他库或现有项目集成,适应不同的项目需求。

应用场景


单页应用程序(SPA):Vue.js 是构建单页应用程序的理想选择。通过 Vue Router 可以轻松实现页面的导航和路由管理,使得用户在不同视图间切换时无需重新加载整个页面,提供更流畅的用户体验。
渐进式Web应用(PWA):ue.js 能够很好地支持 PWA 开发。借助于服务工作者和缓存API等技术,可以创建出具有离线访问能力、快速加载速度以及类似原生应用体验的应用程序。
动态内容更新:Vue.js 提供了高效的双向数据绑定机制,能够实时响应数据的变化并自动更新UI。这使得它非常适合用于需要频繁更新内容的应用场景,如社交网络平台、实时聊天应用等。
移动应用开发:通过与框架如 Ionic 或 Quasar 结合,Vue.js 可以用来开发跨平台的移动应用。这些框架利用 Vue.js 的组件化模型来构建界面,并生成可以在多个平台上运行的代码。
桌面应用开发:Vue.js 还可用于开发桌面应用程序,例如使用 Electron 框架。开发者可以使用熟悉的前端技术栈来创建同时适用于 Windows、Mac 和 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 指令
⚠️不支持控制结构:如 if、for 等复杂逻辑应在模板外处理或使用计算属性

注意事项

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 都兼容。它可以帮助你:

  • 查看组件树结构
  • 检查组件状态(data、props、computed 等)
  • 调试 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 属性值,如 href、src、class、style 等

⭐⭐ 绑定属性

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.js 或 vue.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 数组:用于定义子路由。每个子路由对象都有自己的 path、name 和 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)。每个模块拥有自己的 state、mutation、action、getter。

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)。每个模块拥有自己的 state、mutation、action 和 getter,甚至还可以嵌套子模块。这对于大型应用特别有用,因为它帮助我们更好地组织和管理状态。

独立的状态空间:每个模块都有自己的状态。
命名空间:默认情况下,模块内的 action、mutation 和 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>

模块化使用

当我们使用模块化时,为了更方便地访问模块中的 state、getters、mutations 和 actions,我们需要配合使用 mapState、mapGetters、mapMutations 和 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 提供了一些新的生命周期钩子函数,如 onMounted、onUpdated 和 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 引入了组合式 API(Composition API),这是一个基于函数的 API,用于在组件内部逻辑复用和代码组织。它旨在解决 Vue 2 中选项型 API(Options API)在处理复杂组件时的一些局限性。

setup

在 Vue 3 中,setup 函数是一个新的组件选项,它作为组合式 API(Composition 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 中的 mounted、created 等写法。这些钩子函数必须在组件的 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>

跨层级通信

provide 和 inject 用于跨越多个组件层级传递数据,适合全局状态共享(如主题、用户信息等)。

⭐⭐ 根组件

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 的 组合式 API(Composition API) 中,尤其是在使用 <script setup> 语法时,我们可以通过一些内置的编译器宏来定义组件的行为,比如 props、emits、expose 等。

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

用于设置组件的配置选项,如 name、props、emits 等。如果使用了 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 的数据(state、getters、actions) 是一个常见的操作,尤其是在组件中使用时。但如果不注意,可能会导致 响应性丢失。

⭐⭐ 安全解构

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)提供闪电般的冷启动速度,并支持多种框架(如 Vue、React、Preact 等)。Vite 利用了现代浏览器对 ES 模块的原生支持,避免了传统打包工具(如 Webpack 或 Rollup)需要进行的完整打包过程。

中文文档

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

核心特性


极速冷启动:无需打包编译,直接通过 ESM 加载模块
即时热更新:在开发模式下,修改代码后可以实现毫秒级的更新
开箱即用:内置 TypeScript、JSX、CSS 预处理器等支持
插件系统:可以通过插件扩展功能
生产优化:在生产环境使用 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
  • 文档
    • 菜鸟教程 VUE2:https://www.runoob.com/vue2/vue-tutorial.html
    • 菜鸟教程 VUE3:https://www.runoob.com/vue3/vue3-tutorial.html
    • 官方文档 VUE2:https://v2.cn.vuejs.org/v2/guide
    • 官方文档 VUE3:https://cn.vuejs.org/guide/introduction.html
    • 官方文档 路由:https://v3.router.vuejs.org/zh