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 对于前端开发者来说是非常重要的。

相关推荐
guslegend1 小时前
脚手架原理与本地调试:从 bin 软链接到 npm link
前端·npm·node.js·脚手架·前端工程化
小宋10211 小时前
A2UI从零实战:Agent安全生成交互界面与事件回传
javascript·人工智能·安全·交互
大包子1 小时前
structuredClone vs lodash.cloneDeep:核心差异与选型指南
javascript·vue.js
海码事务所1 小时前
Google Play 新个人开发者账号上架指南:12 人连续 14 天封闭测试怎么做?
前端
田威AI1 小时前
图片内文字翻译的规格:输入输出、保真、自动化、时间与费用
前端·计算机视觉
不可能片场2 小时前
命令行中文变问号 我用环境变量救了场
前端·electron
lerhxx2 小时前
AI 应用中的上下文管理 —— 从"上下文窗口"到"分层压缩"的工程实践
前端·javascript
骑着蜗牛撵大象3272 小时前
多 Agent 分治协作:MapReduce 模式下的并行扇出与结果归并
前端
用户55318297322 小时前
Flutter iOS 热更新深入 Dart VM:读懂函数入口、解释执行与调用桥接
前端
前端snow2 小时前
为什么大厂要用Postgres + mogodb的框架?
前端