vue2 跟 vue3 对比总结
一、 响应式原理差异
1、核心实现
// Vue2 使用 Object.defineProperty 来实现响应式,这是 ES5 的特性
function defineReactive(obj, key, val) {const dep = new Dep();Object.defineProperty(obj, key, {enumerable: true,configurable: true,get() {// 依赖收集if (Dep.target) {dep.depend();}return val;},set(newVal) {if (newVal === val) return;val = newVal;// 通知更新dep.notify();}});
}// Vue3 使用`Proxy`和`Reflect`来实现响应式,这是 ES6 的特性
function reactive(obj) {return new Proxy(obj, {get(target, key, receiver) {const result = Reflect.get(target, key, receiver);track(target, key); // 依赖收集return isObject(result) ? reactive(result) : result;},set(target, key, value, receiver) {const oldValue = target[key];const result = Reflect.set(target, key, value, receiver);if (oldValue !== value) {trigger(target, key); // 触发更新}return result;},deleteProperty(target, key) {const result = Reflect.deleteProperty(target, key);trigger(target, key); // 触发更新return result;}});
}
2、vue2 响应式原理
基于
Object.defineProperty
实现,存在一些限制,如无法检测数组索引变化、对数组的处理需要特殊方法(如push
,pop
,splice
),无法检测对象属性的添加或删除
3、vue3 响应式原理
基于 ES6 的
Proxy和Reflect
实现可以检测到数组索引变化
可以检测到对象属性的添加或删除
性能更好,无需递归遍历对象所有属性
支持 Map、Set、WeakMap 和 WeakSet
二、 生命周期钩子变化
1、钩子名称调整
// Vue2
export default {beforeCreate() {},created() {},beforeMount() {},mounted() {},beforeUpdate() {},updated() {},beforeDestroy() {},destroyed() {}
}// Vue3
export default {setup() {}, // 替代beforeCreate和createdonBeforeMount() {},onMounted() {},onBeforeUpdate() {},onUpdated() {},onBeforeUnmount() {}, // 替代beforeDestroyonUnmounted() {} // 替代destroyed
}
三、 模板语法变化
1、v-model 变化
// Vue 2 中 v-model 本质是 value prop 和 input 事件
<ChildComponent v-model="pageTitle" />
<!-- 等价于 -->
<ChildComponent :value="pageTitle" @input="pageTitle = $event" />// Vue 3 中 v-model 改为 modelValue prop 和 update:modelValue 事件
<ChildComponent v-model="pageTitle" />
<!-- 等价于 -->
<ChildComponent:modelValue="pageTitle"@update:modelValue="pageTitle = $event"
/>
2、支持多个 v-model
<!-- Vue2 -->
<input v-model="message"><!-- Vue3 -->
<MyComponent v-model:value="message" v-model:title="title" />
3、支持多个事件处理器
<!-- Vue2 -->
<button @click="handleClick">点击</button><!-- Vue3 - 支持多个事件处理器 -->
<button @click="handleClick1" @click="handleClick2">点击</button>
4、 片段支持
<!-- Vue2 - 需要1个根节点 -->
<template><div><header>...</header><main>...</main><footer>...</footer></div>
</template><!-- Vue3 - 支持多根节点 -->
<template><header>...</header><main>...</main><footer>...</footer>
</template>
5、允许在 template 上使用 key
<!-- Vue 2 -->
<li v-for="item in list" :key="item.id">{{ item.text }}
</li><!-- Vue 3 -->
<li v-for="item in list" :key="item.id">{{ item.text }}
</li><!-- 但 Vue 3 允许在 template 上使用 key -->
<template v-for="item in list" :key="item.id"><li>{{ item.text }}</li>
</template>
四、异步组件的定义
// vue2
new Vue({components: {AsyncComponent: () => import('./AsyncComponent.vue')}
})// vue3
import { defineAsyncComponent } from 'vue'
const AsyncComponent = defineAsyncComponent(() => import('./AsyncComponent.vue'))
五、 渲染函数的变化
// vue2
export default {render(h) {return h('div', {attrs: {id: 'foo'},on: {click: this.onClick}}, 'hello')}
}// vue3
import { h } from 'vue'
export default {render() {return h('div', {id: 'foo',onClick: this.onClick}, 'hello')}
}
六、过渡类名变化
Vue 2 过渡类名:
.v-enter
进入过渡的开始状态
.v-enter-active
进入过渡的激活状态
.v-enter-to
进入过渡的结束状态
.v-leave
离开过渡的开始状态
.v-leave-active
离开过渡的激活状态
.v-leave-to
离开过渡的结束状态Vue 3 过渡类名:
.v-enter-from
进入过渡的开始状态
.v-enter-active
进入过渡的激活状态
.v-enter-to
进入过渡的结束状态
.v-leave-from
离开过渡的开始状态
.v-leave-active
离开过渡的激活状态
.v-leave-to
离开过渡的结束状态
七、 组件通信
// Vue2
export default {props: ['title'],methods: {handleClick() {this.$emit('update', newValue)}}
}// Vue3
import { defineProps, defineEmits } from 'vue'const props = defineProps(['title'])
const emit = defineEmits(['update'])const handleClick = () => {emit('update', newValue)
}
八、全局API变化
1、应用实例创建
// Vue2
import Vue from 'vue'
import App from './App.vue'new Vue({render: h => h(App)
}).$mount('#app')// Vue3
import { createApp } from 'vue'
import App from './App.vue'createApp(App).mount('#app')
2、全局配置
// Vue2
Vue.config.productionTip = false
Vue.use(VueRouter)
Vue.component('MyComponent', MyComponent)// Vue3
import { createApp } from 'vue'
import App from './App.vue'const app = createApp(App)
app.use(VueRouter)
app.component('MyComponent', MyComponent)
app.mount('#app')
九、 状态管理
1、Vuex (Vue2/Vue3)
// Vuex 4 示例
import { createStore } from 'vuex'export default createStore({state: {count: 0,user: null},mutations: {SET_COUNT(state, count) {state.count = count},SET_USER(state, user) {state.user = user}},actions: {async fetchUser({ commit }) {const user = await api.getUser()commit('SET_USER', user)}},getters: {doubleCount: state => state.count * 2}
})// 在组件中使用
export default {computed: {...mapState(['count', 'user']),...mapGetters(['doubleCount'])},methods: {...mapActions(['fetchUser']),increment() {this.$store.commit('SET_COUNT', this.count + 1)}}
}
2、Pinia (Vue3推荐)
// Pinia 示例
import { defineStore } from 'pinia'export const useCounterStore = defineStore('counter', {state: () => ({count: 0,user: null}),getters: {doubleCount: (state) => state.count * 2},actions: {increment() {this.count++},async fetchUser() {this.user = await api.getUser()}}
})// 在组件中使用
import { useCounterStore } from '@/stores/counter'export default {setup() {const store = useCounterStore()return {count: store.count,doubleCount: store.doubleCount,increment: store.increment,fetchUser: store.fetchUser}}
}
3、vuex和pinia主要区别
特性 | Vuex | Pinia |
---|---|---|
Mutations | 必须通过mutations修改state | 可以直接在actions中修改state,或直接修改 |
TypeScript支持 | 需要额外配置,支持度一般 | 一流的TypeScript支持,完全类型安全 |
模块化 | 需要namespaced模块,较复杂 | 天然模块化,每个store都是独立模块 |
代码组织 | 需要区分mutations/actions | 更简洁,减少模板代码 |
Composition API | 需要额外适配 | 专为Composition API设计 |
包大小 | 较大(约4kB) | 更轻量(约1kB) |
开发体验 | 需要遵循严格模式 | 更灵活,减少约束 |
DevTools支持 | 完善 | 同样完善 |
十、 构建工具对比
1、Webpack (Vue2常用)
// webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')module.exports = {entry: './src/main.js',output: {path: path.resolve(__dirname, 'dist'),filename: 'bundle.js'},module: {rules: [{test: /\.vue$/,loader: 'vue-loader'},{test: /\.js$/,loader: 'babel-loader'}]},plugins: [new HtmlWebpackPlugin({template: './public/index.html'})],devServer: {hot: true,port: 8080}
}
2、Vite (Vue3推荐)
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'export default defineConfig({plugins: [vue()],server: {port: 3000,hot: true},build: {rollupOptions: {output: {manualChunks: {vendor: ['vue', 'vue-router']}}}}
})
3、webpack和vite主要区别
1、核心架构差异
webpack
基于打包器架构:在开发环境下需要先打包所有代码才能启动开发服务器
使用JavaScript作为通用语言:所有文件都需要经过转换和打包
基于插件系统:功能扩展依赖于庞大的插件生态系统
vite
基于原生ES模块:利用浏览器原生支持ES模块的能力
按需编译:只编译当前页面需要的模块,无需打包整个应用
分为开发和生产环境:开发环境使用ES模块,生产环境使用Rollup打包
2、开发服务器启动速度
webpack:随着项目规模增大,启动时间线性增长(可能需要数十秒甚至几分钟)
vte:启动时间几乎与项目规模无关(通常只需几毫秒到几秒)
3、热更新速度(HMR)
webpack:需要重新构建修改的模块及其依赖关系
vite:仅需编译单个文件,HMR更新速度极快(通常<50ms)
4、构建速度
webpack:需要完整打包所有资源
vite:生产环境使用Rollup进行构建,通常比Webpack更快
5、配置复杂度
webpack
配置复杂:需要详细配置loader、plugin、优化选项等
学习曲线陡峭:需要理解各种概念如chunk分割、tree shaking等
灵活性高:几乎可以通过配置实现任何构建需求
vite
开箱即用:默认支持TypeScript、JSX、CSS等,无需复杂配置
配置简单:大多数项目只需极简配置
约定优于配置:提供合理的默认值,减少配置负担
特性 | Webpack | Vite |
---|---|---|
开发服务器启动 | 慢(需完整打包) | 极快(按需编译) |
热更新 | 较慢(重建依赖图) | 极快(单个文件编译) |
配置复杂度 | 高 | 低 |
TypeScript支持 | 需要ts-loader | 原生支持 |
CSS处理 | 需要css-loader等 | 原生支持 |
框架支持 | 通用(需配置) | Vue/React优先 |
生产构建 | Webpack自身 | 使用Rollup |
生态插件 | 极其丰富 | 正在增长 |
十一、 API风格对比
1、Options API (Vue2/Vue3)
代码基于选项的组织方式:将代码按照功能类型分组到不同的选项中(data、methods、computed等)
关注点分离:相同功能的代码可能分散在不同的选项中,大型组件难以维护和理解
// Options API 示例
export default {name: 'UserList',props: {users: {type: Array,default: () => []}},data() {return {searchQuery: '',selectedUser: null,loading: false}},computed: {filteredUsers() {return this.users.filter(user => user.name.toLowerCase().includes(this.searchQuery.toLowerCase()))}},watch: {searchQuery(newVal, oldVal) {console.log('搜索词变化:', oldVal, '->', newVal)}},methods: {async fetchUsers() {this.loading = truetry {const users = await api.getUsers()this.$emit('update:users', users)} catch (error) {console.error('获取用户失败:', error)} finally {this.loading = false}},selectUser(user) {this.selectedUser = userthis.$emit('user-selected', user)}},mounted() {this.fetchUsers()}
}
2、Composition API (Vue3)
基于功能的组织方式:将相关功能的代码组织在一起
逻辑关注点集中:相同功能的代码集中在一个地方
// Composition API 示例
import { ref, computed, watch, onMounted } from 'vue'export default {name: 'UserList',props: {users: {type: Array,default: () => []}},emits: ['update:users', 'user-selected'],setup(props, { emit }) {// 响应式状态const searchQuery = ref('')const selectedUser = ref(null)const loading = ref(false)// 计算属性const filteredUsers = computed(() => {return props.users.filter(user => user.name.toLowerCase().includes(searchQuery.value.toLowerCase()))})// 监听器watch(searchQuery, (newVal, oldVal) => {console.log('搜索词变化:', oldVal, '->', newVal)})// 方法const fetchUsers = async () => {loading.value = truetry {const users = await api.getUsers()emit('update:users', users)} catch (error) {console.error('获取用户失败:', error)} finally {loading.value = false}}const selectUser = (user) => {selectedUser.value = useremit('user-selected', user)}// 生命周期onMounted(() => {fetchUsers()})return {searchQuery,selectedUser,loading,filteredUsers,fetchUsers,selectUser}}
}
3、<script setup>
语法糖
Vue 3.2 引入了更简洁的 <script setup>
语法,直接将setup加到script标签
<script setup>
import { ref, computed, onMounted } from 'vue'const count = ref(0)
const message = ref('Hello')const doubleCount = computed(() => count.value * 2)function increment() {count.value++
}onMounted(() => {console.log('Component mounted')
})
</script>
4、Options API 和Composition API主要区别
特性 | Options API | Composition API |
---|---|---|
代码组织 | 按选项类型分组 | 按逻辑功能分组 |
逻辑复用 | Mixins(有命名冲突问题) | 组合函数(更好的封装和复用) |
TypeScript支持 | 需要额外努力 | 一流的TypeScript支持 |
学习曲线 | 相对平缓,概念简单 | 需要理解响应式API概念 |
灵活性 | 相对固定 | 极高的灵活性 |
代码可读性 | 简单组件中更清晰 | 复杂组件中更清晰 |
this使用 | 大量使用this | 几乎不需要使用this |
规模适应性 | 适合中小型组件 | 特别适合大型复杂组件 |
十二、 路由系统对比
1、Vue Router 3 (Vue2)
// router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'Vue.use(VueRouter)const routes = [{path: '/',name: 'Home',component: Home},{path: '/about',name: 'About',component: () => import('../views/About.vue')}
]const router = new VueRouter({mode: 'history',base: process.env.BASE_URL,routes
})export default router// 在组件中使用
export default {methods: {goToAbout() {this.$router.push('/about')}},computed: {currentRoute() {return this.$route.name}}
}
4、Vue Router 4 (Vue3)
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'const routes = [{path: '/',name: 'Home',component: Home},{path: '/about',name: 'About',component: () => import('../views/About.vue')}
]const router = createRouter({history: createWebHistory(import.meta.env.BASE_URL),routes
})export default router// 在Composition API中使用
import { useRouter, useRoute } from 'vue-router'export default {setup() {const router = useRouter()const route = useRoute()const goToAbout = () => {router.push('/about')}const currentRoute = computed(() => route.name)return {goToAbout,currentRoute}}
}
十三、Vue2 Mixins与Vue3 Hooks
1、Vue 2 Mixins
代码混入机制:将mixin对象的内容合并到组件中
选项式合并:相同选项(data、methods等)会进行特定策略的合并
命名冲突风险:多个mixin之间或与组件之间容易产生命名冲突
关系不清晰:难以追踪属性/方法的来源
// mixin.js
export const userMixin = {data() {return {users: [],loading: false}},methods: {async fetchUsers() {this.loading = truethis.users = await fetch('/api/users')this.loading = false}},mounted() {this.fetchUsers()}
}// 组件中使用
import { userMixin } from './mixins/userMixin'export default {mixins: [userMixin],data() {return {// 可能与mixin中的data产生冲突loading: true // 命名冲突!}}
}
2、Vue3 Hooks
函数式组合:通过函数调用明确地组合逻辑
明确的数据来源:每个hook返回的数据来源清晰
命名空间隔离:hook内部的变量名不会与组件或其他hook冲突
按需使用:可以选择性使用hook返回的内容
// useUsers.js
import { ref, onMounted } from 'vue'export function useUsers() {const users = ref([])const loading = ref(false)const fetchUsers = async () => {loading.value = trueusers.value = await fetch('/api/users')loading.value = false}onMounted(fetchUsers)return {users,loading,fetchUsers}
}// 组件中使用
import { useUsers } from '@/composables/useUsers'export default {setup() {const { users, loading, fetchUsers } = useUsers()// 可以重命名以避免冲突const { users: adminUsers } = useAdminUsers()return {users,loading,fetchUsers}}
}
3、Mixins和hook主要区别
特性 | Vue 2 Mixins | Vue 3 Composition Hooks |
---|---|---|
命名冲突 | 容易发生,难以调试 | 不会发生,可以重命名 |
代码来源 | 不透明,难以追踪 | 明确,易于追踪 |
类型支持 | TypeScript支持有限 | 优秀的TypeScript支持 |
逻辑复用 | 选项式混合,不够灵活 | 函数式组合,非常灵活 |
代码组织 | 逻辑分散在不同选项中 | 相关逻辑集中在一起 |
性能影响 | 所有选项都会被合并 | 按需使用,没有额外合并开销 |
可调试性 | 困难,难以追踪问题来源 | 容易,清晰的调用栈 |
参数传递 | 难以向mixin传递参数 | 可以接受参数,高度可配置 |
十四、Teleport组件
Teleport 是 Vue3 提供的一个内置组件,允许我们将组件模板的一部分 "传送" 到 DOM 树的其他位置,而不受父组件 CSS 样式、定位等影响。
to 属性 (必需):指定目标容器,可以是 CSS 选择器或 DOM 元素
禁用功能:可以使用
:disabled
属性动态控制是否启用传送多个 Teleport 到同一目标:多个 Teleport 可以挂载到同一个目标元素,按顺序追加
<!-- <div class="modal"> 会被渲染到 <body> 元素的末尾,而不是在其父组件的 DOM 位置。--><template><div class="app"><h1>主应用</h1><!-- 将内容传送到 body 元素下 --><Teleport to="body"><div class="modal">这是一个模态框</div></Teleport></div>
</template>
十五、 TypeScript支持
1、 类型推断
// Vue3 - 更好的TypeScript支持
import { ref, computed } from 'vue'interface User {id: numbername: string
}export default {setup() {const user = ref<User>({ id: 1, name: 'John' })const userName = computed(() => user.value.name)return { user, userName }}
}
2、类型安全的props和emits
// Vue3
interface Props {title: stringcount?: number
}interface Emits {(e: 'update', value: string): void(e: 'delete'): void
}const props = defineProps<Props>()
const emit = defineEmits<Emits>()
十六、总结
特性 | Vue2 | Vue3 |
---|---|---|
API 设计 | Options API | Composition API |
状态管理 | Vuex | Pinia |
构建工具 | Webpack | Vite |
响应式系统 | Object.defineProperty | Proxy |
TypeScript 支持 | 有限 | 完全支持 |
性能表现 | 一般 | 提升 30-50% |
包体积 | 较大 | 更小 |
开发体验 | 基础 | 优秀 |
学习成本 | 低 | 中等 |
维护性 | 一般 | 更好 |