初识Vue3
# 初识 Vue3 —— 从 Vue2 到 Vue3 的演变
# 1. Vue 的历史和背景
Vue.js 是由尤雨溪(Evan You)创建的一个渐进式 JavaScript 框架。Vue 的目标是通过简单的 API 和灵活的设计来简化开发者的前端开发体验。Vue 起源于 2013 年,最初是作为一个个人项目来改善自己在使用其他框架时遇到的痛点。Vue 在发布后受到了广泛的关注,特别是在前端界面开发和组件化开发领域。
Vue.js 的版本大致可以分为几个阶段:
- Vue 0.x:最早的版本,功能非常基础,主要是用来试验新概念。
- Vue 1.x:Vue 1.x 于 2015 年发布,开始获得广泛的使用,并且逐渐有了更完整的生态系统。
- Vue 2.x:2016 年 Vue 2.0 发布,成为主流前端框架之一,引入了 虚拟 DOM、响应式数据绑定、组件化 等核心特性,并得到社区的大量支持。Vue 2.x 的成功奠定了其在前端开发中的地位。
- Vue 3.x:Vue 3 在 2020 年正式发布,引入了很多新的特性和 API,并对性能做了全面优化,是一个具有里程碑意义的版本。
# 2. Vue3 新特性概览
在 Vue3 中,最显著的变化包括:
- Composition API:一种新的组件组织方式
- 性能优化:更加高效的响应式系统
- Pinia 作为新的状态管理库:取代 Vuex
- 更好的 TypeScript 支持
- Vue Router 和 Vuex 的变化:更现代化的 API
- Vite 替代 Vue CLI:更高效的开发工具链
# 3. Composition API:新的组件组织方式
组合式 API(Composition API)是一系列 API 的集合,它使我们能够通过函数的方式来组织和编写 Vue 组件,而不再依赖于传统的选项式 API(Options API)。它允许我们在一个地方集中定义组件的状态、逻辑和生命周期钩子,提高了代码的组织性和可复用性。
组合式 API 包括:
- 响应式 API:例如
ref()
和reactive()
,用于创建响应式数据、计算属性和侦听器。 - 生命周期钩子:例如
onMounted()
和onUnmounted()
,在组件的不同生命周期阶段执行逻辑。 - 依赖注入:例如
provide()
和inject()
,在组件中共享响应式状态。
组合式 API 是 Vue 3 的核心特性之一,也可以在 Vue 2.7 及更高版本中使用。对于 Vue 2.x,开发者可以通过官方插件 @vue/composition-api
来引入组合式 API。。
# 3.1 Vue3 引入 Composition API
Composition API 是 Vue3 引入的一种全新的组件设计方式,它允许开发者在一个地方集中定义组件的状态和行为,从而使得代码更加模块化和可重用。它通过 setup()
函数将组件的逻辑组织在一起。
Composition API 示例:
// Vue3 示例:Composition API
<script setup>
import { ref, onMounted } from 'vue'
// 响应式状态
const count = ref(0)
// 用来修改状态、触发更新的函数
function increment() {
count.value++
}
// 生命周期钩子
onMounted(() => {
console.log(`The initial count is ${count.value}.`)
})
</script>
<template>
<button @click="increment">Count is: {{ count }}</button>
</template>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- 简洁性:在 Vue3 中,逻辑集中在
setup()
函数中,所有的状态、方法、计算属性等都可以在这个函数里声明并返回,结构更加清晰。 - 逻辑复用:使用 Composition API,可以将功能模块化到 composable 函数中,方便在多个组件间共享逻辑。
# 3.2 与 Options API 的对比
Vue2 Options API:
// Vue2 示例:Options API
<script>
export default {
// data() 返回的属性将会成为响应式的状态
// 并且暴露在 `this` 上
data() {
return {
count: 0
}
},
// methods 是一些用来更改状态与触发更新的函数
// 它们可以在模板中作为事件处理器绑定
methods: {
increment() {
this.count++
}
},
// 生命周期钩子会在组件生命周期的各个不同阶段被调用
// 例如这个函数就会在组件挂载完成后被调用
mounted() {
console.log(`The initial count is ${this.count}.`)
}
}
</script>
<template>
<button @click="increment">Count is: {{ count }}</button>
</template>
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
Vue3 Composition API 相比于 Options API,更加灵活,允许开发者将状态、方法、计算属性等逻辑集中管理,易于维护和复用。
# 4. 响应式系统
Vue 3 的响应式系统利用 Proxy 代替了 Vue 2 中的 Object.defineProperty
,使得响应式数据的处理更为高效和灵活。核心概念包括 ref()
和 reactive()
,它们用于声明响应式状态。通过这些工具,Vue 能够追踪状态的变化,并自动更新视图。
# 4.1 声明响应式状态
在 Vue 3 中,声明响应式状态的两种主要方式是通过 ref()
和 reactive()
:
ref()
用于声明基本数据类型(如字符串、数字、布尔值等)的响应式状态。reactive()
用于将对象(包括嵌套对象和数组)转换为响应式状态。
# ref()
的使用
ref()
用于声明一个简单的响应式引用,它返回一个包含 .value
属性的对象。你可以通过 .value
访问或修改它的值。
import { ref } from 'vue';
export default {
setup() {
const count = ref(0); // count 是一个响应式变量,初始值为 0
const increment = () => {
count.value++; // 修改 count 的值
};
return { count, increment };
}
};
2
3
4
5
6
7
8
9
10
11
12
13
在模板中,ref
会自动解包,因此我们不需要直接访问 .value
,而是可以直接使用 count
:
<template>
<button @click="increment">{{ count }}</button>
</template>
2
3
# 为什么要使用 ref()
?
ref()
让 Vue 可以追踪基本数据类型的变化。例如,当你修改 count.value
时,Vue 会自动更新 DOM。这种设计让 Vue 具有高效的响应式更新能力。你可能会好奇,为什么要用 .value
?这是因为 Vue 使用 Proxy
来追踪状态变化,而 ref()
则通过 .value
来检测变化。
# 深层响应性
ref()
本身支持深层响应性。即便你声明一个包含嵌套对象或数组的 ref
,Vue 会追踪对象内部的每一个属性或数组元素的变化。
import { ref } from 'vue';
const state = ref({
nested: { count: 0 },
arr: ['apple', 'banana']
});
// 修改嵌套属性时,Vue 会触发视图更新
state.value.nested.count++;
state.value.arr.push('orange');
2
3
4
5
6
7
8
9
10
如果不希望 Vue 跟踪嵌套对象的每个变化,可以使用 浅层 ref()
,它只会追踪 .value
的变化,而不会递归地追踪对象内部的变化。
# 4.2 reactive()
的使用
reactive()
是另一种声明响应式状态的方式,它用于将整个对象变成响应式对象。它返回一个 Proxy,能够代理对象的所有属性,包括嵌套属性。
import { reactive } from 'vue';
const state = reactive({
count: 0,
user: { name: 'Alice' }
});
// 修改对象的属性时,Vue 会触发视图更新
state.count++;
state.user.name = 'Bob';
2
3
4
5
6
7
8
9
10
reactive()
适用于处理复杂对象和嵌套数据结构,而 ref()
通常用于基本数据类型。如果你的数据是对象或数组,reactive()
会自动将其转换为响应式。
# Reactive Proxy vs. Original
需要注意的是,reactive()
返回的是一个 Proxy,它与原始对象并不相等。更改原始对象不会触发视图更新,只有通过 reactive()
返回的 Proxy 对象才会生效。
const raw = { count: 0 };
const proxy = reactive(raw);
console.log(proxy === raw); // false
2
3
4
这意味着你应该始终使用通过 reactive()
返回的代理对象,而不是原始对象来进行操作。
# 4.3 reactive()
的局限性
尽管 reactive()
很强大,但它也有一些局限性:
- 只能用于对象:
reactive()
只能用于对象类型的值(包括数组、对象、Map、Set 等)。它不能用于原始类型(如 string、number 或 boolean)。 - 无法替换整个对象:由于 Vue 使用属性访问来追踪变化,替换整个对象会导致原始引用的响应性丧失。例如:
let state = reactive({ count: 0 });
state = reactive({ count: 1 }); // 这里丢失了原始的响应性引用
2
- 不适用于解构:如果你解构了一个响应式对象的属性,Vue 将无法追踪这些属性的变化。为了避免这种情况,你应该传递整个对象而不是解构后的属性。
const state = reactive({ count: 0 });
let { count } = state; // 这里解构会导致 count 不再是响应式的
count++; // 不会更新原始的 state
2
3
# 4.4 ref()
解包与 reactive()
的结合
当 ref()
被作为 reactive()
对象的属性时,它会自动解包其 .value
。这意味着你可以直接访问 state.count
,而无需显式地使用 .value
。
const count = ref(0);
const state = reactive({
count
});
console.log(state.count); // 0
state.count = 1;
console.log(count.value); // 1
2
3
4
5
6
7
8
但是,ref
只会在顶级属性中自动解包。如果它嵌套在对象或数组内,你需要显式访问 .value
。
# 4.5 数组和集合的注意事项
当 ref()
被作为数组或原生集合(如 Map 或 Set)中的元素时,它不会被自动解包。这意味着你仍然需要使用 .value
来访问和修改其值。
const books = reactive([ref('Vue 3 Guide')]);
console.log(books[0].value); // 使用 .value 来访问
const map = reactive(new Map([['count', ref(0)]]));
console.log(map.get('count').value); // 使用 .value 来访问
2
3
4
5
# 4.6 在模板中解包的注意事项
在模板中,只有 顶级的 ref
属性会被自动解包。如果 ref
嵌套在对象中,Vue 不会自动解包它,因此你需要显式地解构或访问 .value
。
const count = ref(0);
const object = { id: ref(1) };
// 这是有效的
{{ count + 1 }}
// 这不会按预期工作
{{ object.id + 1 }} // 结果是 [object Object]1,因为 object.id 没有解包
// 正确的做法是解构 id
const { id } = object;
{{ id + 1 }} // 结果是 2
2
3
4
5
6
7
8
9
10
11
12
# 4. Pinia状态管理库
Vue2 中的 Vuex 是官方推荐的状态管理库。虽然 Vuex 提供了强大的功能,但其 API 相对繁琐,尤其是在大型应用中,很多时候需要大量的样板代码(boilerplate)。此外,Vuex 在 Vue3 中的 Composition API 支持并不完美,开发者需要编写大量的类型声明,且代码结构相对复杂。
# 4.1 Vue3 引入 Pinia 作为新的状态管理库
Vue3 官方推荐 Pinia 作为新的状态管理解决方案,Pinia 是 Vue3 的官方状态管理库,拥有简洁的 API,更好地与 Composition API 集成,并且原生支持 TypeScript。
Pinia 示例:
// 安装 Pinia
npm install pinia
// 创建 store
import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
actions: {
increment() {
this.count++;
}
}
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- 更简洁的 API:通过
defineStore()
创建 store,而不是 Vuex 中的state
、mutations
和actions
等繁琐的结构。 - 与 Composition API 更加契合:Pinia 的 API 设计与 Vue3 的 Composition API 紧密结合,使得状态管理变得更加直观。
# 4.2 与 Vuex 的对比
特性 | Vuex | Pinia |
---|---|---|
API 复杂度 | 较复杂,尤其是 mutations 和 actions | 简洁,采用 defineStore() 定义 store |
TypeScript 支持 | 需要手动类型声明,较复杂 | 原生支持 TypeScript,自动类型推导 |
响应式支持 | 基于 Vue 2 的响应式系统 | 基于 Vue 3 的 Proxy 响应式系统,性能更好 |
开发体验 | 需要多层次的配置和管理,学习曲线较陡峭 | 轻量、易于上手,集成 Composition API |
状态持久化 | 可以通过插件实现(如 vuex-persistedstate) | 支持内置持久化功能 |
# 5. 更好的 TypeScript 支持
Vue2 支持 TypeScript,但需要手动配置类型声明,且 API 设计并不完全兼容 TypeScript。开发者需要额外编写大量类型定义文件来使 Vue 和 Vuex 支持 TypeScript,这在一定程度上降低了开发效率。
# 5.1 Vue3 原生支持 TypeScript
Vue3 在设计时就将 TypeScript 作为一个核心需求,所有的 API 都与 TypeScript 紧密集成。Vue3 的所有核心 API 都具备良好的类型推导,避免了 Vue2 中繁琐的类型声明工作。
Vue3 TypeScript 示例:
import { defineComponent, ref } from 'vue';
export default defineComponent({
setup() {
const count = ref<number>(0); // 使用 TypeScript 定义类型
const increment = () => {
count.value++;
};
return { count, increment };
}
});
2
3
4
5
6
7
8
9
10
11
12
13
- 自动类型推导:在 Vue3 中,
ref()
和reactive()
都具有自动的类型推导,开发者无需手动声明类型。 - TypeScript 更友好:Vue3 的 API 更加兼容 TypeScript,开发者可以在使用时获得更多的类型提示和静态检查,减少类型错误。
# 6. Vite 替代 Vue CLI
Vue2 使用 Vue CLI 作为脚手架工具,Vue CLI 基于 Webpack,配置复杂,构建速度较慢,尤其是在开发过程中,由于热更新(HMR)的效率较低,开发体验较差。
# 6.1 Vue3 引入 Vite 替代 Vue CLI
Vue3 推荐使用 Vite,Vite 是一个基于原生 ES 模块的构建工具,极大地提高了开发中的热更新和构建速度,尤其适合现代 JavaScript 和 Vue3 项目的开发。
Vite 示例:
# 使用 Vite 创建 Vue3 项目
npm create vite@latest my-vue-app --template vue
cd my-vue-app
npm install
npm run dev
2
3
4
5
- 零秒冷启动:Vite 在开发模式下能够做到 零秒冷启动,大幅提高开发效率。
- 原生支持模块化:Vite 使用浏览器原生的 ES 模块,而不是传统的打包方式,使得开发过程更加轻便。
# 7. 总结
Vue3 的发布带来了许多重要的变化,从 Composition API 到 Pinia,再到 TypeScript 和 Vite,每个变化都使得开发者能够编写更高效、简洁、易于维护的代码。如果你还在使用 Vue2,是时候考虑迁移到 Vue3 了。希望这篇文章能够帮助你深入理解 Vue3 的新特性,并为你的开发过程提供更多的指导和灵感。
← Nginx Vue3数据双向绑定→