针对Vue2转Vue3开发者 / 刚接触框架的新手,快速掌握核心差异与实战技巧
vue2 是选项式 API的写法,vue3 是支持选项式和组合式两种写法,
建议大家都用组合式(见第十条):
Vue 2 生命周期 | Vue 3 选项式 API | Vue 3 组合式 API | 解释 |
---|---|---|---|
beforeCreate |
beforeCreate |
setup() |
在实例初始化之后,数据观测 data 和 event/watcher 事件配置之前被调用。此阶段无法访问 data 中的数据和 methods 中的方法。 |
created |
created |
setup() |
实例已经创建完成之后被调用。在这一步,实例已经完成了数据观测 data 、property 和 method 的计算、watch/event 事件回调。然而,挂载阶段还没有开始,$el 属性目前不可用。 |
beforeMount |
beforeMount |
onBeforeMount |
在挂载开始之前被调用:相关的 render 函数首次被调用。此时模板还未挂载到页面上。 |
mounted |
mounted |
onMounted |
实例已经挂载之后调用。此时模板已经编译完成并挂载到页面上,可以访问 $el 元素。 |
beforeUpdate |
beforeUpdate |
onBeforeUpdate |
在数据更新之前被调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。 |
updated |
updated |
onUpdated |
由于数据更改导致的虚拟 DOM 重新渲染和打补丁之后调用。此时 DOM 已经更新,可以执行依赖于更新后的 DOM 的操作。 |
beforeDestroy |
beforeUnmount |
onBeforeUnmount |
在实例销毁之前调用。在这一步,实例仍然完全可用。在 Vue 3 中,“销毁” 概念改为 “卸载”。 |
destroyed |
unmounted |
onUnmounted |
在实例销毁之后调用。调用后,所有的事件监听器和子实例都已经被销毁。在 Vue 3 中对应实例卸载之后。 |
钩子 | Vue2 | Vue3组合式API | 触发时机 |
---|---|---|---|
setup() | ❌ 无 | ✅ 核心入口 | 组件初始化时立即执行,替代beforeCreate和created |
onMounted | mounted | onMounted | 组件挂载完成后执行(可访问DOM) |
onUnmounted | destroyed | onUnmounted | 组件卸载后清理副作用 |
{{ msg }}
功能 | Vue2选项式API | Vue3组合式API |
---|---|---|
定义props | props: ['msg'] |
defineProps(['msg']) |
定义事件 | emits: ['update'] |
defineEmits(['update']) |
触发事件 | this.$emit('update', value) |
emit('update', value) |
响应式数据 | data() { return { count: 0 } } |
const count = ref(0) |
语法糖
自动暴露顶层绑定
无需return
模板可用数据
更简洁的API组织方式
defineProps/defineEmits
// 带类型提示的进阶写法
const props = defineProps({
msg: {
type: String,
required: true
}
})
const emit = defineEmits<{
(e: 'update', value: string): void
}>()
模板直接访问
{{ msg }}
快速访问子组件实例(模板ref)
依赖注入(provide/inject)
// 祖先组件
import { provide, ref } from 'vue'
const counter = ref(0)
provide('globalCounter', counter)
// 后代组件
import { inject } from 'vue'
const counter = inject('globalCounter')
作用域插槽示例:
{{ user.name }}
特性 | ref | reactive |
---|---|---|
支持类型 | 所有类型 | 对象/数组 |
模板访问 | 自动解包 | 直接访问 |
重新赋值 | ✅ 保持响应 | ❌ 丢失响应 |
解构响应 | 需.value访问 | 需toRefs包裹 |
// 基本类型用ref
const count = ref(0)
// 复杂对象用reactive
const user = reactive({
name: 'Alice',
age: 25
})
// 函数返回用ref保持响应
const state = ref({
data: null,
loading: true
})
Vue3 通过异步更新队列优化 DOM 更新性能,其核心逻辑如下:
批量更新:数据变更时,Vue 不会立即更新 DOM,而是将变更推入队列,在下一个事件循环中批量执行17
去重优化:同一数据的多次修改仅保留最终值,避免重复计算8
执行时机:优先使用微任务(Promise/MutationObserver),降级为宏任务(setTimeout)
// 示例:多次修改仅触发一次更新
const count = ref(0)
count.value++ // 修改1
count.value++ // 修改2
// DOM 更新只执行一次
获取最新 DOM:在数据变更后,通过 nextTick
确保回调在 DOM 更新后执行79
实现原理:利用 Promise 微任务队列(优先级:Promise > MutationObserver > setTimeout)
import { nextTick } from 'vue'
async function updateData() {
data.value = '新值'
await nextTick()
console.log('DOM已更新:', document.getElementById('element'))
}
概念 | 作用 | Vue3 特性 |
---|---|---|
State | 全局状态存储 | 支持 Composition API 访问 |
Mutations | 同步修改状态(原子操作) | 必须通过 commit 触发 |
Actions | 异步操作 + 提交 Mutations | 支持 async/await 语法 |
Getters | 计算属性派生 | 类似组件的 computed |
// store.js
import { createStore } from 'vuex'
export default createStore({
state: { user: null },
mutations: {
SET_USER(state, payload) {
state.user = payload
}
},
actions: {
async fetchUser({ commit }) {
const user = await api.getUser()
commit('SET_USER', user)
}
}
})
// 组件中使用
import { useStore } from 'vuex'
setup() {
const store = useStore()
const user = computed(() => store.state.user)
const login = async () => {
await store.dispatch('fetchUser')
}
return { user, login }
}
// router.js
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/',
component: () => import('@/views/Home.vue'), // 动态导入
meta: { requiresAuth: true }
},
{
path: '/about',
component: () => import(/* webpackChunkName: "about" */ '@/views/About.vue')
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
// 全局前置守卫
router.beforeEach((to, from) => {
if (to.meta.requiresAuth && !store.state.user) {
return '/login'
}
})
// 组合式 API 中使用
import { onBeforeRouteLeave } from 'vue-router'
setup() {
onBeforeRouteLeave((to, from) => {
// 离开路由前的逻辑
})
}
列表头尾插入效率提升50%
大数据量列表渲染优化30%
静态节点标记跳过比对
// 异步组件定义
const AsyncComp = defineAsyncComponent(() => import('./Component.vue'))
// 路由配置
{ path: '/dashboard', component: () => import('@/views/Dashboard.vue') }
使用 Pinia(Vuex 的替代方案)简化状态管理
模块化拆分 Store,避免单一文件过大
// 错误示例
data.value = '新值'
document.getElementById('element').textContent = data.value // 可能获取旧 DOM
// 正确方案
data.value = '新值'
nextTick(() => {
document.getElementById('element').textContent = data.value
})
import { watch } from 'vue'
import { useRoute } from 'vue-router'
setup() {
const route = useRoute()
watch(
() => route.params.id,
(newId) => {
fetchData(newId)
}
)
}
// 更灵活的逻辑组织
export default {
setup() {
const count = ref(0)
// 可复用的逻辑片段
const { data } = useFetch('/api/data')
// 生命周期聚合
onMounted(() => {
console.log('组件已挂载')
})
return { count, data }
}
}
特性 | Vue2 | Vue3改进 |
---|---|---|
响应式系统 | Object.defineProperty | Proxy实现 |
模板指令 | v-for优先级高于v-if | v-if优先级高于v-for |
Fragment组件 | ❌ 不支持 | ✅ 支持多根节点 |
Teleport组件 | ❌ 无 | ✅ 跨DOM层级传送内容 |
interface User {
id: number
name: string
}
const user = reactive({
id: 1,
name: 'Alice'
})
// 带类型提示的ref
const count = ref(0)
// 类型安全的emit
const emit = defineEmits<{
(e: 'update', value: number): void
}>()
工具 | 用途 | 命令示例 |
---|---|---|
Vite | 极速开发环境 | npm create vite@latest |
Pinia | 新一代状态管理 | npm install pinia |
VueUse | 常用工具集合 | npm install @vueuse/core |
Vitest | 单元测试框架 | npm install -D vitest |
学习路线建议:先掌握组合式API -> 熟悉响应式原理 -> 实践常用生态工具 -> 深入性能优化技巧