vue2与vue3的差异在哪里?

Vue2 与 Vue3 全面对比分析

1. Vue2 与 Vue3 的核心差异

1.1 响应式系统

Vue2:使用 Object.defineProperty 实现响应式

javascript 复制代码
// Vue2 响应式原理简化实现
function defineReactive(obj, key, val) {
  // 递归处理嵌套对象
  if (typeof val === 'object' && val !== null) {
    observe(val)
  }
  
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get() {
      // 依赖收集
      console.log('get: ', val)
      return val
    },
    set(newVal) {
      if (newVal !== val) {
        // 清除旧依赖
        console.log('set: ', newVal)
        // 递归处理新值
        if (typeof newVal === 'object' && newVal !== null) {
          observe(newVal)
        }
        val = newVal
        // 触发更新
      }
    }
  })
}

function observe(obj) {
  if (typeof obj !== 'object' || obj === null) {
    return
  }
  
  Object.keys(obj).forEach(key => {
    defineReactive(obj, key, obj[key])
  })
}

Vue3:使用 Proxy + Reflect 实现响应式

javascript 复制代码
// Vue3 响应式原理简化实现
function reactive(obj) {
  // 只处理对象类型
  if (typeof obj !== 'object' || obj === null) {
    return obj
  }
  
  return new Proxy(obj, {
    get(target, key, receiver) {
      const result = Reflect.get(target, key, receiver)
      // 依赖收集
      console.log('get: ', key)
      // 递归处理嵌套对象
      if (typeof result === 'object' && result !== null) {
        return reactive(result)
      }
      return result
    },
    set(target, key, value, receiver) {
      const oldValue = target[key]
      const result = Reflect.set(target, key, value, receiver)
      // 触发更新
      if (oldValue !== value) {
        console.log('set: ', key, value)
      }
      return result
    },
    deleteProperty(target, key) {
      const hadKey = Reflect.has(target, key)
      const result = Reflect.deleteProperty(target, key)
      // 触发更新
      if (hadKey) {
        console.log('delete: ', key)
      }
      return result
    },
    has(target, key) {
      const result = Reflect.has(target, key)
      console.log('has: ', key)
      return result
    },
    ownKeys(target) {
      const result = Reflect.ownKeys(target)
      console.log('ownKeys: ', result)
      return result
    }
  })
}

差异对比

特性 Vue2 (Object.defineProperty) Vue3 (Proxy)
监听新增属性 不支持(需要使用 Vue.set) 原生支持
监听删除属性 不支持(需要使用 Vue.delete) 原生支持
监听数组索引 不支持 原生支持
监听数组长度 不支持 原生支持
嵌套对象处理 初始化时递归遍历,性能较差 访问时懒递归,性能更好
类型支持 只支持对象 支持对象和数组
错误处理 无法捕获 可以通过 Reflect 捕获

性能对比

  • 初始化性能:Vue3 更快,因为它采用懒递归策略,只在访问时处理嵌套对象
  • 更新性能:Vue3 更快,因为 Proxy 的拦截操作比 Object.defineProperty 更高效
  • 内存占用:Vue3 更低,因为它不需要为每个属性创建单独的 getter/setter

使用场景

  • Vue2:适合小型应用,对响应式数据的操作相对简单
  • Vue3:适合大型应用,需要处理复杂的响应式数据结构和操作

1.2 组合式 API (Composition API)

Vue2:使用选项式 API (Options API)

javascript 复制代码
// Vue2 Options API
export default {
  data() {
    return {
      count: 0,
      message: 'Hello',
      user: {
        name: 'John',
        age: 30
      }
    }
  },
  methods: {
    increment() {
      this.count++
    },
    updateUser() {
      this.user.name = 'Jane'
    }
  },
  computed: {
    doubleCount() {
      return this.count * 2
    },
    userName() {
      return this.user.name
    }
  },
  watch: {
    count(newVal, oldVal) {
      console.log('count changed:', newVal, oldVal)
    },
    'user.name'(newVal, oldVal) {
      console.log('user name changed:', newVal, oldVal)
    }
  },
  mounted() {
    console.log('组件挂载')
  }
}

Vue3:新增组合式 API

javascript 复制代码
// Vue3 Composition API
import { ref, computed, watch, reactive, onMounted } from 'vue'

export default {
  setup() {
    // 基本类型响应式数据
    const count = ref(0)
    const message = ref('Hello')
    
    // 对象类型响应式数据
    const user = reactive({
      name: 'John',
      age: 30
    })
    
    // 方法
    const increment = () => {
      count.value++
    }
    
    const updateUser = () => {
      user.name = 'Jane'
    }
    
    // 计算属性
    const doubleCount = computed(() => count.value * 2)
    const userName = computed(() => user.name)
    
    // 监听器
    watch(count, (newVal, oldVal) => {
      console.log('count changed:', newVal, oldVal)
    })
    
    watch(() => user.name, (newVal, oldVal) => {
      console.log('user name changed:', newVal, oldVal)
    })
    
    // 生命周期钩子
    onMounted(() => {
      console.log('组件挂载')
    })
    
    return {
      count,
      message,
      user,
      increment,
      updateUser,
      doubleCount,
      userName
    }
  }
}

// 或者使用 script setup(Vue 3.2+)
<script setup>
import { ref, computed, watch, reactive, onMounted } from 'vue'

// 基本类型响应式数据
const count = ref(0)
const message = ref('Hello')

// 对象类型响应式数据
const user = reactive({
  name: 'John',
  age: 30
})

// 方法
const increment = () => {
  count.value++
}

const updateUser = () => {
  user.name = 'Jane'
}

// 计算属性
const doubleCount = computed(() => count.value * 2)
const userName = computed(() => user.name)

// 监听器
watch(count, (newVal, oldVal) => {
  console.log('count changed:', newVal, oldVal)
})

watch(() => user.name, (newVal, oldVal) => {
  console.log('user name changed:', newVal, oldVal)
})

// 生命周期钩子
onMounted(() => {
  console.log('组件挂载')
})
</script>

组合式 API 的优势

  1. 更好的代码组织

    • 相关逻辑可以放在一起,而不是分散在不同的选项中
    • 更易于理解和维护大型组件
  2. 更好的逻辑复用

    • 可以通过组合函数(composables)复用逻辑
    • 替代了 Vue2 中的 mixins,避免了命名冲突和来源不明的问题
  3. 更好的类型推断

    • TypeScript 支持更好,类型推断更准确
    • 减少了类型定义的工作量
  4. 更好的 tree-shaking

    • 只导入使用的 API,减少打包体积
    • 提高应用性能
  5. 更灵活的逻辑组合

    • 可以根据需要组合多个逻辑
    • 更适合复杂的业务场景

组合式 API 与选项式 API 的对比

特性 选项式 API 组合式 API
代码组织 按选项分类 按逻辑分类
逻辑复用 mixins,可能有命名冲突 组合函数,无命名冲突
TypeScript 支持 部分支持 原生支持
打包体积 固定大小 按需导入
学习曲线 简单 稍复杂
适用场景 小型组件 大型组件和应用

组合式函数示例

javascript 复制代码
// composables/useApi.js
import { ref, onUnmounted } from 'vue'

export function useApi(url) {
  const data = ref(null)
  const loading = ref(false)
  const error = ref(null)
  const controller = new AbortController()
  
  const fetchData = async () => {
    loading.value = true
    error.value = null
    
    try {
      const response = await fetch(url, {
        signal: controller.signal
      })
      data.value = await response.json()
    } catch (err) {
      if (err.name !== 'AbortError') {
        error.value = err.message
      }
    } finally {
      loading.value = false
    }
  }
  
  // 初始加载
  fetchData()
  
  // 组件卸载时取消请求
  onUnmounted(() => {
    controller.abort()
  })
  
  return {
    data,
    loading,
    error,
    refetch: fetchData
  }
}

使用组合式函数

vue 复制代码
<script setup>
import { useApi } from '@/composables/useApi'

const { data, loading, error, refetch } = useApi('https://api.example.com/data')
</script>

<template>
  <div>
    <div v-if="loading">加载中...</div>
    <div v-else-if="error">错误: {{ error }}</div>
    <div v-else-if="data">
      <pre>{{ data }}</pre>
      <button @click="refetch">重新加载</button>
    </div>
  </div>
</template>

1.3 生命周期钩子

Vue2 生命周期 Vue3 生命周期
beforeCreate setup()
created setup()
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeDestroy onBeforeUnmount
destroyed onUnmounted
activated onActivated
deactivated onDeactivated

Vue3 生命周期使用示例

javascript 复制代码
import { onMounted, onUpdated, onUnmounted } from 'vue'

export default {
  setup() {
    onMounted(() => {
      console.log('组件挂载')
    })
    
    onUpdated(() => {
      console.log('组件更新')
    })
    
    onUnmounted(() => {
      console.log('组件卸载')
    })
  }
}

1.4 模板语法

Vue3 新增特性

  1. 多根节点模板
vue 复制代码
<!-- Vue2 只能有一个根节点 -->
<template>
  <div>
    <h1>标题</h1>
    <p>内容</p>
  </div>
</template>

<!-- Vue3 支持多根节点 -->
<template>
  <h1>标题</h1>
  <p>内容</p>
</template>

多根节点的优势

  • 减少不必要的 DOM 嵌套
  • 更灵活的组件结构
  • 更好的与其他库集成
  1. Fragment 组件

Fragment 是 Vue3 中的一个内置组件,用于包裹多个元素而不产生额外的 DOM 节点。

vue 复制代码
<!-- 使用 Fragment -->
<template>
  <Fragment>
    <h1>标题</h1>
    <p>内容</p>
  </Fragment>
</template>

<!-- 简写形式(默认就是 Fragment) -->
<template>
  <h1>标题</h1>
  <p>内容</p>
</template>
  1. Teleport 组件

Teleport 用于将组件的内容渲染到指定的 DOM 节点中,而不是当前组件的 DOM 结构中。

使用场景

  • 弹窗、对话框
  • 通知、提示
  • 模态框
vue 复制代码
<!-- Teleport 示例 -->
<template>
  <div>
    <h1>组件内容</h1>
    <button @click="showModal = true">打开弹窗</button>
    
    <Teleport to="#modal-container">
      <div v-if="showModal" class="modal">
        <div class="modal-content">
          <h2>弹窗标题</h2>
          <p>弹窗内容</p>
          <button @click="showModal = false">关闭</button>
        </div>
      </div>
    </Teleport>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const showModal = ref(false)
</script>

<style scoped>
.modal {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
}

.modal-content {
  background-color: white;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
</style>
  1. Suspense 组件

Suspense 用于处理异步组件的加载状态,提供了更好的用户体验。

使用场景

  • 加载异步组件
  • 处理 Promise 数据
  • 优化首屏加载体验
vue 复制代码
<!-- Suspense 示例 -->
<template>
  <div>
    <h1>用户列表</h1>
    <Suspense>
      <template #default>
        <AsyncUserList />
      </template>
      <template #fallback>
        <div class="loading">
          <div class="spinner"></div>
          <p>加载用户列表中...</p>
        </div>
      </template>
    </Suspense>
  </div>
</template>

<script setup>
// 异步组件
const AsyncUserList = defineAsyncComponent(() => import('./UserList.vue'))
</script>

<style scoped>
.loading {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 40px;
}

.spinner {
  width: 40px;
  height: 40px;
  border: 4px solid #f3f3f3;
  border-top: 4px solid #3498db;
  border-radius: 50%;
  animation: spin 1s linear infinite;
  margin-bottom: 16px;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}
</style>
  1. v-memo 指令

Vue3.2+ 新增的指令,用于缓存模板表达式的结果,优化渲染性能。

使用场景

  • 渲染大量列表项
  • 复杂的计算表达式
  • 性能敏感的组件
vue 复制代码
<template>
  <div>
    <h1>商品列表</h1>
    <ul>
      <li v-for="item in items" :key="item.id" v-memo="[item.price, item.stock]">
        <h3>{{ item.name }}</h3>
        <p>价格: {{ item.price }}</p>
        <p>库存: {{ item.stock }}</p>
        <p>折扣: {{ calculateDiscount(item.price, item.stock) }}</p>
      </li>
    </ul>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'

const items = ref([
  { id: 1, name: '商品1', price: 100, stock: 50 },
  { id: 2, name: '商品2', price: 200, stock: 30 },
  { id: 3, name: '商品3', price: 150, stock: 20 }
])

const calculateDiscount = (price, stock) => {
  // 复杂的折扣计算逻辑
  console.log('计算折扣')
  return stock > 40 ? price * 0.9 : price
}
</script>
  1. 其他模板语法改进
  • 更灵活的 v-if 和 v-for 组合:Vue3 允许在同一元素上使用 v-if 和 v-for(但仍建议优先使用计算属性)
  • 改进的 v-model:Vue3 统一了 v-model 的用法,支持自定义修饰符
  • 更好的 slot 语法:Vue3 推荐使用 v-slot 指令,替代旧的 slot 语法
  • 动态组件的改进:Vue3 中的 支持更灵活的组件切换

v-model 改进示例

vue 复制代码
<!-- Vue2 -->
<template>
  <div>
    <input v-model="message">
    <custom-input v-model="value" :value="value" @input="value = $event"></custom-input>
  </div>
</template>

<!-- Vue3 -->
<template>
  <div>
    <input v-model="message">
    <custom-input v-model="value"></custom-input>
    <custom-input v-model:count="count" v-model:message="message"></custom-input>
  </div>
</template>

1.5 其他差异

特性 Vue2 Vue3
构建工具 webpack + vue-loader Vite (默认) / webpack
TypeScript 支持 部分支持 原生支持
打包体积 较大 更小 (Tree-shaking 优化)
性能 良好 更好 (编译优化)
自定义指令 bind, inserted, update, componentUpdated, unbind beforeMount, mounted, beforeUpdate, updated, beforeUnmount, unmounted

2. 为什么要从 Vue2 升级到 Vue3

2.1 性能提升

  1. 响应式系统性能:Proxy 相比 Object.defineProperty 具有更好的性能,尤其是在处理大型对象时
  2. 编译优化
    • 静态提升 (Static Hoisting)
    • 预字符串化 (Pre-stringification)
    • 缓存事件处理函数
    • 区块树 (Block Tree) 优化
  3. 更小的打包体积:通过 Tree-shaking 移除未使用的代码,核心库体积减小约 41%

2.2 开发体验改善

  1. 组合式 API

    • 更好的代码组织和复用
    • 逻辑关注点分离
    • 更适合大型项目
    • 更好的 TypeScript 支持
  2. 更强大的模板语法

    • 多根节点
    • 新的内置组件 (Fragment, Teleport, Suspense)
    • 更好的错误处理
  3. 开发者工具

    • Vue DevTools v6+ 专门为 Vue3 优化
    • 更好的调试体验

2.3 长期支持

  • Vue2 将于 2023 年 12 月 31 日停止维护
  • Vue3 是未来的主要版本,将获得持续的更新和支持
  • 生态系统正在向 Vue3 迁移

2.4 其他优势

  1. 更好的 TypeScript 集成:Vue3 使用 TypeScript 重写,提供了更完善的类型定义
  2. Composition API 与 Options API 并存:可以渐进式迁移
  3. 新的内置函数 :如 toRef, toRefs, isRef, unref
  4. 更好的异步组件支持

3. Vue2 与 Vue3 的打包工具

3.1 Vue2 打包工具

Vue2 主要使用 webpack + vue-loader,通过 Vue CLI 进行配置管理。

详细配置示例

javascript 复制代码
// vue.config.js (Vue CLI 配置)
const path = require('path')

function resolve(dir) {
  return path.join(__dirname, dir)
}

module.exports = {
  // 部署应用包的基本 URL
  publicPath: process.env.NODE_ENV === 'production' ? '/production-sub-path/' : '/',
  
  // 生产环境构建文件的目录
  outputDir: 'dist',
  
  // 放置生成的静态资源的目录
  assetsDir: 'static',
  
  // 指定生成的 index.html 的输出路径
  indexPath: 'index.html',
  
  // 是否在开发环境下通过 eslint-loader 在每次保存时 lint 代码
  lintOnSave: process.env.NODE_ENV === 'development',
  
  // 是否生成生产环境的 source map
  productionSourceMap: false,
  
  // 开发服务器配置
  devServer: {
    port: 8080,
    open: true,
    overlay: {
      warnings: false,
      errors: true
    },
    // 代理配置
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
        pathRewrite: {
          '^/api': ''
        }
      }
    }
  },
  
  // webpack 配置
  configureWebpack: {
    // 提供全局变量
    plugins: [
      // 自定义插件
    ],
    // 优化配置
    optimization: {
      splitChunks: {
        chunks: 'all',
        cacheGroups: {
          vendor: {
            name: 'vendor',
            test: /[\\/]node_modules[\\/]/,
            priority: 10,
            chunks: 'initial'
          },
          common: {
            name: 'common',
            minChunks: 2,
            priority: 5,
            chunks: 'initial',
            reuseExistingChunk: true
          }
        }
      }
    }
  },
  
  // 链式 webpack 配置
  chainWebpack: config => {
    // 别名配置
    config.resolve.alias
      .set('@', resolve('src'))
      .set('components', resolve('src/components'))
      .set('views', resolve('src/views'))
      .set('assets', resolve('src/assets'))
      .set('utils', resolve('src/utils'))
    
    // 图片处理
    config.module
      .rule('images')
      .use('url-loader')
      .loader('url-loader')
      .tap(options => {
        options.limit = 10240
        return options
      })
    
    // 开发环境配置
    if (process.env.NODE_ENV === 'development') {
      config.devtool('cheap-module-eval-source-map')
    }
    
    // 生产环境配置
    if (process.env.NODE_ENV === 'production') {
      config.optimization.minimize(true)
    }
  }
}

webpack 基础配置示例

javascript 复制代码
// webpack.config.js
const path = require('path')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')

module.exports = {
  // 入口文件
  entry: './src/main.js',
  
  // 输出配置
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[hash:8].js',
    chunkFilename: '[name].[hash:8].chunk.js'
  },
  
  // 模块配置
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        exclude: /node_modules/
      },
      {
        test: /\.css$/,
        use: [
          process.env.NODE_ENV === 'production' 
            ? MiniCssExtractPlugin.loader 
            : 'vue-style-loader',
          'css-loader'
        ]
      },
      {
        test: /\.(scss|sass)$/,
        use: [
          process.env.NODE_ENV === 'production' 
            ? MiniCssExtractPlugin.loader 
            : 'vue-style-loader',
          'css-loader',
          'sass-loader'
        ]
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: 'static/img/[name].[hash:7].[ext]'
        }
      },
      {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: 'static/media/[name].[hash:7].[ext]'
        }
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: 'static/fonts/[name].[hash:7].[ext]'
        }
      }
    ]
  },
  
  // 插件配置
  plugins: [
    new VueLoaderPlugin(),
    new HtmlWebpackPlugin({
      template: 'public/index.html',
      filename: 'index.html',
      inject: true,
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeAttributeQuotes: true
      }
    }),
    new CleanWebpackPlugin(),
    new MiniCssExtractPlugin({
      filename: 'static/css/[name].[hash:8].css',
      chunkFilename: 'static/css/[name].[hash:8].chunk.css'
    })
  ],
  
  // 解析配置
  resolve: {
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': path.resolve(__dirname, 'src')
    },
    extensions: ['*', '.js', '.vue', '.json']
  },
  
  // 开发服务器配置
  devServer: {
    contentBase: './dist',
    port: 8080,
    hot: true,
    overlay: {
      warnings: false,
      errors: true
    },
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
        pathRewrite: {
          '^/api': ''
        }
      }
    }
  },
  
  // 优化配置
  optimization: {
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: {
            drop_console: true
          }
        }
      }),
      new OptimizeCSSAssetsPlugin({})
    ],
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          name: 'vendor',
          test: /[\\/]node_modules[\\/]/,
          priority: 10
        },
        common: {
          name: 'common',
          minChunks: 2,
          priority: 5
        }
      }
    }
  }
}

3.2 Vue3 打包工具

Vue3 默认使用 Vite,也支持 webpack。Vite 是一个现代前端构建工具,提供了更快的开发体验。

3.2.1 Vite 配置

创建 Vue3 项目

bash 复制代码
# npm
npm create vite@latest my-vue3-app -- --template vue

# yarn
yarn create vite my-vue3-app --template vue

# pnpm
pnpm create vite my-vue3-app --template vue

# 创建带 TypeScript 的 Vue3 项目
npm create vite@latest my-vue3-app -- --template vue-ts

详细 Vite 配置示例

javascript 复制代码
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
import { createHtmlPlugin } from 'vite-plugin-html'
import { viteMockServe } from 'vite-plugin-mock'
import vueJsx from '@vitejs/plugin-vue-jsx'
import autoprefixer from 'autoprefixer'

export default defineConfig(({ mode }) => {
  const isProduction = mode === 'production'
  
  return {
    // 项目根目录
    root: process.cwd(),
    
    // 公共基础路径
    base: isProduction ? '/production-sub-path/' : '/',
    
    // 插件配置
    plugins: [
      vue(),
      // JSX 支持
      vueJsx(),
      // HTML 插件
      createHtmlPlugin({
        inject: {
          data: {
            title: 'Vue3 App',
            injectScript: `<script src="/inject-script.js"></script>`
          }
        },
        minify: isProduction
      }),
      // Mock 支持
      viteMockServe({
        mockPath: './mock',
        localEnabled: !isProduction,
        prodEnabled: isProduction,
        injectCode: `
          import { setupProdMockServer } from './mockProdServer';
          setupProdMockServer();
        `
      })
    ],
    
    // 解析配置
    resolve: {
      // 别名配置
      alias: {
        '@': path.resolve(__dirname, './src'),
        'components': path.resolve(__dirname, './src/components'),
        'views': path.resolve(__dirname, './src/views'),
        'assets': path.resolve(__dirname, './src/assets'),
        'utils': path.resolve(__dirname, './src/utils')
      },
      // 扩展��配置
      extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
    },
    
    // 开发服务器配置
    server: {
      // 端口
      port: 3000,
      // 自动打开浏览器
      open: true,
      // 服务器主机
      host: '0.0.0.0',
      // 代理配置
      proxy: {
        '/api': {
          target: 'http://localhost:8080',
          changeOrigin: true,
          rewrite: (path) => path.replace(/^\/api/, ''),
          // 配置 WebSocket 代理
          ws: true
        }
      },
      // 服务器响应头
      headers: {
        'Access-Control-Allow-Origin': '*'
      }
    },
    
    // 构建配置
    build: {
      // 输出目录
      outDir: 'dist',
      // 静态资源目录
      assetsDir: 'assets',
      // 生成源码映射
      sourcemap: !isProduction,
      // 代码压缩
      minify: 'terser',
      // Terser 配置
      terserOptions: {
        compress: {
          drop_console: isProduction,
          drop_debugger: isProduction
        }
      },
      // 资源内联
      assetsInlineLimit: 4096,
      // CSS 代码分割
      cssCodeSplit: true,
      // 预加载
      preload: {
        include: 'auto'
      },
      // 产物文件名
      rollupOptions: {
        output: {
          // 手动代码分割
          manualChunks: {
            vendor: ['vue'],
            router: ['vue-router'],
            store: ['pinia'],
            ui: ['element-plus']
          },
          // 产物命名
          entryFileNames: 'js/[name].[hash:8].js',
          chunkFileNames: 'js/[name].[hash:8].chunk.js',
          assetFileNames: {
            css: 'css/[name].[hash:8].css',
            img: 'img/[name].[hash:8].[ext]',
            fonts: 'fonts/[name].[hash:8].[ext]'
          }
        }
      }
    },
    
    // CSS 配置
    css: {
      // 启用 CSS 模块
      modules: {
        localsConvention: 'camelCaseOnly'
      },
      // PostCSS 配置
      postcss: {
        plugins: [
          autoprefixer({
            overrideBrowserslist: ['> 1%', 'last 2 versions']
          })
        ]
      },
      // 全局 CSS 变量
      preprocessorOptions: {
        scss: {
          additionalData: `@import "@/assets/styles/variables.scss";`
        }
      }
    },
    
    // 环境变量
    define: {
      'process.env': {
        NODE_ENV: JSON.stringify(mode)
      }
    }
  }
})
3.2.2 Vite vs webpack 详细对比
特性 Vite webpack
开发服务器 基于 ES 模块,启动极快(毫秒级) 基于打包,启动较慢(秒级)
热更新 毫秒级更新,只更新修改的模块 较慢,需要重新打包
构建 使用 Rollup,产物优化,速度快 使用自己的打包器,功能丰富
配置复杂度 简单,默认配置合理 复杂,需要详细配置
依赖预构建 使用 esbuild,速度快 使用 babel,速度较慢
Tree-shaking 天然支持,效果更好 需要配置,效果一般
代码分割 自动代码分割,配置简单 需要详细配置
适用场景 中小型项目,开发体验优先 大型项目,需要复杂配置
生态系统 快速增长,插件逐渐丰富 成熟稳定,插件丰富
浏览器兼容性 现代浏览器,需要 polyfill 支持旧浏览器

性能对比

项目 Vite webpack 提升比例
启动时间 ~100ms ~3000ms ~96.7%
热更新时间 ~10ms ~500ms ~98%
构建时间 ~2000ms ~5000ms ~60%
3.2.3 Vue3 + webpack 配置

Vue CLI 4.5+ 支持创建 Vue3 项目

bash 复制代码
vue create my-vue3-app
# 选择 Vue 3 选项

详细配置示例

javascript 复制代码
// vue.config.js
const path = require('path')

module.exports = {
  // 部署应用包的基本 URL
  publicPath: process.env.NODE_ENV === 'production' ? '/production-sub-path/' : '/',
  
  // 生产环境构建文件的目录
  outputDir: 'dist',
  
  // 放置生成的静态资源的目录
  assetsDir: 'static',
  
  // 指定生成的 index.html 的输出路径
  indexPath: 'index.html',
  
  // 是否在开发环境下通过 eslint-loader 在每次保存时 lint 代码
  lintOnSave: process.env.NODE_ENV === 'development',
  
  // 是否生成生产环境的 source map
  productionSourceMap: false,
  
  // 开发服务器配置
  devServer: {
    port: 8080,
    open: true,
    overlay: {
      warnings: false,
      errors: true
    },
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
        pathRewrite: {
          '^/api': ''
        }
      }
    }
  },
  
  // 配置 webpack
  configureWebpack: {
    // 提供全局变量
    plugins: [
      // 自定义插件
    ],
    // 优化配置
    optimization: {
      splitChunks: {
        chunks: 'all',
        cacheGroups: {
          vendor: {
            name: 'vendor',
            test: /[\\/]node_modules[\\/]/,
            priority: 10
          },
          common: {
            name: 'common',
            minChunks: 2,
            priority: 5
          }
        }
      }
    }
  },
  
  // 链式 webpack 配置
  chainWebpack: config => {
    // 别名配置
    config.resolve.alias
      .set('@', path.resolve(__dirname, 'src'))
      .set('components', path.resolve(__dirname, 'src/components'))
      .set('views', path.resolve(__dirname, 'src/views'))
    
    // 图片处理
    config.module
      .rule('images')
      .use('url-loader')
      .loader('url-loader')
      .tap(options => {
        options.limit = 10240
        return options
      })
    
    // 开发环境配置
    if (process.env.NODE_ENV === 'development') {
      config.devtool('cheap-module-eval-source-map')
    }
    
    // 生产环境配置
    if (process.env.NODE_ENV === 'production') {
      config.optimization.minimize(true)
    }
  }
}
3.2.4 打包工具选择建议

选择 Vite 的场景

  • 新项目,特别是中小型项目
  • 开发体验要求高,需要快速的热更新
  • 主要面向现代浏览器
  • 希望简化配置

选择 webpack 的场景

  • 大型项目,需要复杂的构建配置
  • 必须支持旧浏览器
  • 已经熟悉 webpack 配置
  • 需要使用特定的 webpack 插件

混合使用

  • 开发环境使用 Vite,生产环境使用 webpack
  • 根据项目的不同部分选择合适的构建工具

4. 从 Vue2 迁移到 Vue3 的详细步骤

4.1 准备工作

  1. 项目评估

    • 分析项目规模和复杂度
    • 识别使用的 Vue2 特性和第三方库
    • 制定迁移计划和时间表
  2. 环境准备

    • 确保 Node.js 版本 >= 12.0.0
    • 更新 npm 或 yarn 到最新版本
    • 创建项目备份
  3. 兼容性检查

    • 使用 @vue/compat 工具检查代码兼容性
    bash 复制代码
    npm install @vue/compat
    • 配置 vue.config.js 使用兼容模式
    javascript 复制代码
    // vue.config.js
    module.exports = {
      configureWebpack: {
        resolve: {
          alias: {
            'vue': '@vue/compat'
          }
        }
      }
    }
  4. 依赖更新

    bash 复制代码
    # 卸载旧依赖
    npm uninstall vue vue-router vuex
    
    # 安装新依赖
    npm install vue@next vue-router@next vuex@next
    
    # 安装其他必要的依赖
    npm install @vue/compiler-sfc

4.2 代码迁移

4.2.1 全局配置迁移

Vue2

javascript 复制代码
// main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

// 全局配置
Vue.config.productionTip = false
Vue.config.devtools = true

// 全局组件
Vue.component('MyComponent', MyComponent)

// 全局指令
Vue.directive('focus', focusDirective)

// 全局混合
Vue.mixin(myMixin)

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

Vue3

javascript 复制代码
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

// 创建应用实例
const app = createApp(App)

// 全局配置
app.config.productionTip = false
app.config.devtools = true

// 全局组件
app.component('MyComponent', MyComponent)

// 全局指令
app.directive('focus', focusDirective)

// 全局属性
app.config.globalProperties.$myProperty = 'value'

// 使用插件
app.use(router)
app.use(store)

// 挂载应用
app.mount('#app')
4.2.2 组件迁移
  1. 响应式数据
javascript 复制代码
// Vue2
export default {
  data() {
    return {
      count: 0,
      user: {
        name: 'John',
        age: 30
      }
    }
  },
  methods: {
    updateCount() {
      this.count++
    },
    updateUser() {
      this.user.name = 'Jane'
    }
  }
}

// Vue3 (Options API 兼容)
export default {
  data() {
    return {
      count: 0,
      user: {
        name: 'John',
        age: 30
      }
    }
  },
  methods: {
    updateCount() {
      this.count++
    },
    updateUser() {
      this.user.name = 'Jane'
    }
  }
}

// Vue3 (Composition API)
import { ref, reactive } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const user = reactive({
      name: 'John',
      age: 30
    })
    
    const updateCount = () => {
      count.value++
    }
    
    const updateUser = () => {
      user.name = 'Jane'
    }
    
    return {
      count,
      user,
      updateCount,
      updateUser
    }
  }
}

// Vue3 (script setup)
<script setup>
import { ref, reactive } from 'vue'

const count = ref(0)
const user = reactive({
  name: 'John',
  age: 30
})

const updateCount = () => {
  count.value++
}

const updateUser = () => {
  user.name = 'Jane'
}
</script>
  1. 生命周期钩子
javascript 复制代码
// Vue2
export default {
  beforeCreate() {
    console.log('组件即将创建')
  },
  created() {
    console.log('组件创建完成')
  },
  beforeMount() {
    console.log('组件即将挂载')
  },
  mounted() {
    console.log('组件挂载完成')
  },
  beforeUpdate() {
    console.log('组件即将更新')
  },
  updated() {
    console.log('组件更新完成')
  },
  beforeDestroy() {
    console.log('组件即将销毁')
  },
  destroyed() {
    console.log('组件销毁完成')
  }
}

// Vue3 (Options API 兼容)
export default {
  beforeCreate() {
    console.log('组件即将创建')
  },
  created() {
    console.log('组件创建完成')
  },
  beforeMount() {
    console.log('组件即将挂载')
  },
  mounted() {
    console.log('组件挂载完成')
  },
  beforeUpdate() {
    console.log('组件即将更新')
  },
  updated() {
    console.log('组件更新完成')
  },
  beforeUnmount() {
    console.log('组件即将卸载')
  },
  unmounted() {
    console.log('组件卸载完成')
  }
}

// Vue3 (Composition API)
import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue'

export default {
  setup() {
    onBeforeMount(() => {
      console.log('组件即将挂载')
    })
    
    onMounted(() => {
      console.log('组件挂载完成')
    })
    
    onBeforeUpdate(() => {
      console.log('组件即将更新')
    })
    
    onUpdated(() => {
      console.log('组件更新完成')
    })
    
    onBeforeUnmount(() => {
      console.log('组件即将卸载')
    })
    
    onUnmounted(() => {
      console.log('组件卸载完成')
    })
  }
}
  1. 计算属性和监听器
javascript 复制代码
// Vue2
export default {
  data() {
    return {
      count: 0
    }
  },
  computed: {
    doubleCount() {
      return this.count * 2
    },
    formattedCount() {
      return `Count: ${this.count}`
    }
  },
  watch: {
    count(newVal, oldVal) {
      console.log('count changed:', newVal, oldVal)
    },
    'user.name'(newVal, oldVal) {
      console.log('user name changed:', newVal, oldVal)
    }
  }
}

// Vue3 (Options API 兼容)
export default {
  data() {
    return {
      count: 0
    }
  },
  computed: {
    doubleCount() {
      return this.count * 2
    },
    formattedCount() {
      return `Count: ${this.count}`
    }
  },
  watch: {
    count(newVal, oldVal) {
      console.log('count changed:', newVal, oldVal)
    }
  }
}

// Vue3 (Composition API)
import { ref, computed, watch } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const user = ref({ name: 'John' })
    
    const doubleCount = computed(() => count.value * 2)
    const formattedCount = computed(() => `Count: ${count.value}`)
    
    watch(count, (newVal, oldVal) => {
      console.log('count changed:', newVal, oldVal)
    })
    
    watch(() => user.value.name, (newVal, oldVal) => {
      console.log('user name changed:', newVal, oldVal)
    })
    
    // 深度监听
    watch(user, (newVal, oldVal) => {
      console.log('user changed:', newVal, oldVal)
    }, { deep: true })
    
    return {
      count,
      user,
      doubleCount,
      formattedCount
    }
  }
}
  1. 自定义指令
javascript 复制代码
// Vue2
export default {
  directives: {
    focus: {
      bind(el, binding, vnode) {
        // 指令绑定到元素时
      },
      inserted(el, binding, vnode) {
        // 元素插入到 DOM 时
        el.focus()
      },
      update(el, binding, vnode, oldVnode) {
        // 组件更新时
      },
      componentUpdated(el, binding, vnode, oldVnode) {
        // 组件更新完成时
      },
      unbind(el, binding, vnode) {
        // 指令从元素上解绑时
      }
    }
  }
}

// Vue3
export default {
  directives: {
    focus: {
      beforeMount(el, binding, vnode) {
        // 指令绑定到元素且元素即将挂载时
      },
      mounted(el, binding, vnode) {
        // 元素插入到 DOM 时
        el.focus()
      },
      beforeUpdate(el, binding, vnode, oldVnode) {
        // 组件更新前
      },
      updated(el, binding, vnode, oldVnode) {
        // 组件更新后
      },
      beforeUnmount(el, binding, vnode) {
        // 元素即将从 DOM 中移除时
      },
      unmounted(el, binding, vnode) {
        // 元素从 DOM 中移除后
      }
    }
  }
}
4.2.3 路由和状态管理迁移

Vue Router 迁移

javascript 复制代码
// Vue2 (router/index.js)
import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/views/Home.vue'

Vue.use(Router)

export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'Home',
      component: Home
    },
    {
      path: '/about',
      name: 'About',
      component: () => import('@/views/About.vue')
    }
  ]
})

// 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(process.env.BASE_URL),
  routes
})

export default router

Vuex 迁移

javascript 复制代码
// Vue2 (store/index.js)
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment(state) {
      state.count++
    }
  },
  actions: {
    incrementAsync({ commit }) {
      setTimeout(() => {
        commit('increment')
      }, 1000)
    }
  },
  getters: {
    doubleCount(state) {
      return state.count * 2
    }
  }
})

// Vue3 (store/index.js)
import { createStore } from 'vuex'

export default createStore({
  state: {
    count: 0
  },
  mutations: {
    increment(state) {
      state.count++
    }
  },
  actions: {
    incrementAsync({ commit }) {
      setTimeout(() => {
        commit('increment')
      }, 1000)
    }
  },
  getters: {
    doubleCount(state) {
      return state.count * 2
    }
  }
})

// 或者使用 Pinia (Vue3 推荐的状态管理)
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0
  }),
  getters: {
    doubleCount: (state) => state.count * 2
  },
  actions: {
    increment() {
      this.count++
    },
    incrementAsync() {
      setTimeout(() => {
        this.count++
      }, 1000)
    }
  }
})

4.3 渐进式迁移策略

  1. 使用 Composition API 插件 :在 Vue2 中使用 @vue/composition-api 插件

    bash 复制代码
    npm install @vue/composition-api
    javascript 复制代码
    // main.js
    import Vue from 'vue'
    import VueCompositionAPI from '@vue/composition-api'
    
    Vue.use(VueCompositionAPI)
  2. 逐步迁移组件

    • 先迁移简单组件,再迁移复杂组件
    • 按功能模块分组迁移
    • 为每个迁移的组件编写测试
  3. 使用构建工具

    • 利用 Vite 或 Vue CLI 的构建工具进行迁移
    • 配置别名确保代码路径正确
    • 使用构建工具的分析功能优化打包
  4. 测试策略

    • 单元测试:确保每个组件的功能正常
    • 集成测试:确保组件间的交互正常
    • E2E 测试:确保整个应用的流程正常
  5. 常见问题和解决方案

问题 原因 解决方案
响应式数据不更新 Vue3 的响应式系统变化 使用 ref 或 reactive 替代直接赋值
生命周期钩子不执行 生命周期钩子名称变化 更新为 Vue3 的生命周期钩子名称
自定义指令不工作 自定义指令钩子变化 更新为 Vue3 的自定义指令钩子
路由配置错误 Vue Router API 变化 使用 createRouter 和 createWebHistory
状态管理错误 Vuex API 变化 使用 createStore 或迁移到 Pinia
第三方库不兼容 库尚未支持 Vue3 查找替代库或使用兼容模式
  1. 迁移工具
    • vue-codemod:自动转换 Vue2 代码到 Vue3
    • eslint-plugin-vue:检查 Vue3 代码规范
    • Vue DevTools:调试 Vue3 应用

4.4 迁移后的优化

  1. 代码优化

    • 使用 Composition API 重构复杂组件
    • 利用 Tree-shaking 减少打包体积
    • 优化响应式数据结构
  2. 性能优化

    • 使用 Vite 提升开发体验
    • 配置合理的代码分割
    • 优化静态资源加载
  3. 最佳实践

    • 采用新的 Vue3 特性和 API
    • 遵循 Vue3 的代码规范
    • 利用 TypeScript 提升代码质量
  4. 监控和调试

    • 配置错误监控
    • 利用 Vue DevTools 进行调试
    • 定期进行性能分析

5. 最佳实践

5.1 Vue3 项目结构

复制代码
src/
├── assets/          # 静态资源
├── components/      # 公共组件
├── composables/     # 组合式函数
├── router/          # 路由配置
├── store/           # 状态管理
├── utils/           # 工具函数
├── views/           # 页面组件
├── App.vue          # 根组件
└── main.js          # 入口文件

5.2 组合式 API 最佳实践

  1. 逻辑组织:将相关逻辑放在同一个组合函数中
javascript 复制代码
// composables/useCounter.js
import { ref, computed, watch } from 'vue'

export function useCounter(initialValue = 0) {
  const count = ref(initialValue)
  const doubleCount = computed(() => count.value * 2)
  
  const increment = () => count.value++
  const decrement = () => count.value--
  const reset = () => count.value = initialValue
  
  watch(count, (newVal) => {
    console.log('Count changed:', newVal)
  })
  
  return {
    count,
    doubleCount,
    increment,
    decrement,
    reset
  }
}
  1. 使用 script setup:简化代码
vue 复制代码
<script setup>
import { useCounter } from '@/composables/useCounter'

const { count, doubleCount, increment, decrement } = useCounter(0)
</script>

<template>
  <div>
    <p>Count: {{ count }}</p>
    <p>Double Count: {{ doubleCount }}</p>
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
  </div>
</template>

5.3 性能优化技巧

  1. 使用 Teleport:将 DOM 元素移动到其他位置,避免 CSS 层级问题
  2. 使用 Suspense:处理异步组件,提供更好的加载体验
  3. 合理使用 ref 和 reactive
    • 基本类型使用 ref
    • 对象类型使用 reactive
  4. 使用 shallowRef 和 shallowReactive:对于大型对象,避免深层响应式
  5. 使用 markRaw:标记不需要响应式的对象
  6. 优化渲染
    • 使用 v-memo 缓存渲染结果
    • 使用 v-once 渲染静态内容
    • 使用 computed 缓存计算结果

6. 总结

Vue3 作为 Vue 的下一代版本,带来了许多重要的改进:

  1. 性能提升:响应式系统优化、编译优化、更小的打包体积
  2. 开发体验:组合式 API、更好的 TypeScript 支持、更强大的模板语法
  3. 工具链:Vite 作为默认构建工具,提供更快的开发体验
  4. 生态系统:逐步完善,越来越多的库支持 Vue3

虽然从 Vue2 迁移到 Vue3 需要一定的工作量,但长期来看,这些投入是值得的。Vue3 不仅解决了 Vue2 中的一些痛点,还为未来的前端开发提供了更好的基础。

对于新项目,建议直接使用 Vue3;对于现有项目,可以根据实际情况制定渐进式迁移策略,逐步享受 Vue3 带来的好处。

7. 附录

7.1 常用 API 对比

功能 Vue2 Vue3
创建应用 new Vue() createApp()
全局方法 Vue.use() app.use()
全局组件 Vue.component() app.component()
全局指令 Vue.directive() app.directive()
过滤器 支持 移除(使用计算属性或方法替代)
事件总线 Vue.prototype.$bus mitt 或其他库

7.2 推荐学习资源

  1. 官方文档

  2. 教程

  3. 工具

  4. 社区


通过本文的对比分析,希望能帮助您更好地理解 Vue2 和 Vue3 的差异,以及如何从 Vue2 迁移到 Vue3。Vue3 代表了前端框架的发展方向,掌握 Vue3 对于前端开发者来说是非常重要的。

相关推荐
Irene19912 小时前
JavaScript字符串转数字方法总结
javascript·隐式转换
笔画人生2 小时前
Cursor + 蓝耘API:用自然语言完成全栈项目开发
前端·后端
AC赳赳老秦2 小时前
外文文献精读:DeepSeek翻译并解析顶会论文核心技术要点
前端·flutter·zookeeper·自动化·rabbitmq·prometheus·deepseek
小宇的天下2 小时前
Calibre 3Dstack --每日一个命令day18【floating_trace】(3-18)
服务器·前端·数据库
毕设源码-钟学长2 小时前
【开题答辩全过程】以 基于web技术的酒店信息管理系统设计与实现-为例,包含答辩的问题和答案
前端
css趣多多2 小时前
this.$watch
前端·javascript·vue.js
Code小翊3 小时前
JS语法速查手册,一遍过JS
javascript
干前端3 小时前
Vue3虚拟滚动列表组件进阶:不定高度及原理分析!!!
前端·前端组件
子春一3 小时前
Flutter for OpenHarmony:构建一个 Flutter 天气卡片组件,深入解析动态 UI、响应式布局与语义化设计
javascript·flutter·ui