vue2 跟 vue3 对比总结

发布于:2025-09-03 ⋅ 阅读:(13) ⋅ 点赞:(0)

一、 响应式原理差异

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 实现,存在一些限制,如无法检测数组索引变化、对数组的处理需要特殊方法(如 pushpopsplice),无法检测对象属性的添加或删除

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和created
  onBeforeMount() {},
  onMounted() {},
  onBeforeUpdate() {},
  onUpdated() {},
  onBeforeUnmount() {}, // 替代beforeDestroy
  onUnmounted() {}      // 替代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 = true
      try {
        const users = await api.getUsers()
        this.$emit('update:users', users)
      } catch (error) {
        console.error('获取用户失败:', error)
      } finally {
        this.loading = false
      }
    },
    selectUser(user) {
      this.selectedUser = user
      this.$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 = true
      try {
        const users = await api.getUsers()
        emit('update:users', users)
      } catch (error) {
        console.error('获取用户失败:', error)
      } finally {
        loading.value = false
      }
    }
    
    const selectUser = (user) => {
      selectedUser.value = user
      emit('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 = true
      this.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 = true
    users.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: number
  name: 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: string
  count?: 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%
包体积 较大 更小
开发体验 基础 优秀
学习成本 中等
维护性 一般 更好


网站公告

今日签到

点亮在社区的每一天
去签到