vue2和vue3有什么区别,用法上有哪些区别

Vue 2 和 Vue 3 在写法哲学 上就有根本差异:Vue 2 是 Options API (按 data / methods / computed 等选项分类写),Vue 3 主推 Composition API + <script setup>(按业务逻辑聚合写),同时 Vue 3 也兼容 Options API。下面通过大量对照示例,把核心用法差异讲透。


一、创建应用实例

javascript 复制代码
// Vue 2
import Vue from 'vue'
import App from './App.vue'

new Vue({
  render: h => h(App)
}).$mount('#app')
javascript 复制代码
// Vue 3
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

// 全局 API 从 Vue.xxx 改为 app.xxx
app.component('MyComp', MyComp)
app.directive('focus', focusDirective)
app.use(router)
app.mount('#app')

关键变化:

  • new Vue() → createApp()
  • 全局 API(Vue.component、Vue.use、Vue.directive)改为 app 实例方法
  • Vue.prototype.$http = ... → app.config.globalProperties.$http = ...

二、组件写法:Options API vs Composition API

1. 计数器组件对照

Vue 2(Options API):

vue 复制代码
<template>
  <div>
    <p>计数:{{ count }}</p>
    <p>双倍:{{ double }}</p>
    <button @click="increment">增加</button>
    <button @click="reset">重置</button>
  </div>
</template>

<script>
export default {
  data() {
    return { count: 0 }
  },
  computed: {
    double() {
      return this.count * 2
    }
  },
  methods: {
    increment() {
      this.count++
    },
    reset() {
      this.count = 0
    }
  },
  mounted() {
    console.log('组件挂载完成', this.count)
  }
}
</script>

Vue 3(Composition API + setup()):

vue 复制代码
<template>
  <div>
    <p>计数:{{ count }}</p>
    <p>双倍:{{ double }}</p>
    <button @click="increment">增加</button>
    <button @click="reset">重置</button>
  </div>
</template>

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

export default {
  setup() {
    const count = ref(0)
    const double = computed(() => count.value * 2)

    const increment = () => {
      count.value++
    }
    const reset = () => {
      count.value = 0
    }

    onMounted(() => {
      console.log('组件挂载完成', count.value)
    })

    return { count, double, increment, reset }
  }
}
</script>

Vue 3(<script setup> 语法糖,最推荐):

vue 复制代码
<template>
  <div>
    <p>计数:{{ count }}</p>
    <p>双倍:{{ double }}</p>
    <button @click="increment">增加</button>
    <button @click="reset">重置</button>
  </div>
</template>

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

const count = ref(0)
const double = computed(() => count.value * 2)

const increment = () => {
  count.value++
}
const reset = () => {
  count.value = 0
}

onMounted(() => {
  console.log('组件挂载完成', count.value)
})
</script>

💡 <script setup> 的优势 :无需 return,顶层变量/函数/导入的组件自动暴露给模板,代码量减少 30%+,TypeScript 支持更好。

2. 响应式数据:ref vs reactive

javascript 复制代码
// Vue 2:所有响应式数据都写在 data() 里
data() {
  return {
    count: 0,
    user: { name: '张三' }
  }
}
// 动态添加属性需要 this.$set
this.$set(this.user, 'age', 28)
javascript 复制代码
// Vue 3:Proxy 代理,直接赋值即响应
import { ref, reactive } from 'vue'

// 基本类型用 ref,访问需 .value
const count = ref(0)
count.value++  // 修改
console.log(count.value)  // 读取

// 对象/数组用 reactive
const user = reactive({ name: '张三' })
user.age = 28  // 直接生效,无需 $set

// reactive 对象直接解构会丢失响应式,需用 toRefs
const { name, age } = toRefs(user)

⚠️ 新手最容易踩的坑:

  • ref 在 JS 中必须写 .value,模板中自动解包不需要
  • reactive 不能直接解构,否则响应式丢失,要用 toRefs()

3. 计算属性与侦听器

javascript 复制代码
// Vue 2
computed: {
  fullName() {
    return this.firstName + ' ' + this.lastName
  }
},
watch: {
  count(newVal, oldVal) {
    console.log(`count 从 ${oldVal} 变为 ${newVal}`)
  }
}
javascript 复制代码
// Vue 3
import { computed, watch, watchEffect } from 'vue'

const fullName = computed(() => firstName.value + ' ' + lastName.value)

// 侦听单个源
watch(count, (newVal, oldVal) => {
  console.log(`count 从 ${oldVal} 变为 ${newVal}`)
})

// 侦听多个源
watch([count, name], ([newCount, newName]) => {
  console.log(`count=${newCount}, name=${newName}`)
})

// watchEffect:自动收集依赖
watchEffect(() => {
  console.log(`count 变化时触发:${count.value}`)
})

4. 生命周期钩子

Vue 2 Vue 3 Options API Vue 3 Composition API
beforeCreate beforeCreate -(setup 替代)
created created -(setup 替代)
beforeMount beforeMount onBeforeMount
mounted mounted onMounted
beforeUpdate beforeUpdate onBeforeUpdate
updated updated onUpdated
beforeDestroy beforeDestroy onBeforeUnmount
destroyed destroyed onUnmounted
javascript 复制代码
// Vue 3 <script setup>
import { onMounted, onBeforeUnmount } from 'vue'

onMounted(() => {
  console.log('挂载')
})

onBeforeUnmount(() => {
  console.log('卸载前清理')
})

⚠️ 生命周期名称变化 :beforeDestroy → onBeforeUnmount,destroyed → onUnmounted。Vue 3 的生命周期钩子必须从 vue 显式导入。


三、组件通信

1. Props 与 Emits

Vue 2:

vue 复制代码
<!-- 子组件 -->
<script>
export default {
  props: {
    title: { type: String, required: true },
    count: { type: Number, default: 0 }
  },
  methods: {
    handleClick() {
      this.$emit('update', 123)
    }
  }
}
</script>

Vue 3(<script setup>):

vue 复制代码
<script setup>
const props = defineProps({
  title: { type: String, required: true },
  count: { type: Number, default: 0 }
})

const emit = defineEmits(['update'])

const handleClick = () => {
  emit('update', 123)
}

// 使用 TS 类型声明(推荐)
// const props = defineProps<{
//   title: string
//   count?: number
// }>()
</script>

📌 defineProps 和 defineEmits 是编译器宏 ,在 <script setup> 中自动可用,无需导入。

2. v-model 变化(破坏性变更)

html 复制代码
<!-- Vue 2:v-model 本质是 :value + @input -->
<Child v-model="value" />
<!-- 等价于 -->
<Child :value="value" @input="value = $event" />

<!-- 多值同步需要用 .sync 修饰符 -->
<Child :title.sync="title" />
html 复制代码
<!-- Vue 3:v-model 默认是 :modelValue + @update:modelValue -->
<Child v-model="value" />
<!-- 等价于 -->
<Child :modelValue="value" @update:modelValue="value = $event" />

<!-- 支持多个 v-model 绑定 -->
<Child v-model:title="title" v-model:content="content" />

<!-- .sync 被移除,统一用 v-model:xxx -->

子组件接收:

javascript 复制代码
// Vue 3 子组件
const props = defineProps(['modelValue', 'title'])
const emit = defineEmits(['update:modelValue', 'update:title'])

3. 跨组件通信:provide / inject

javascript 复制代码
// Vue 3 祖先组件
import { provide, ref } from 'vue'

const count = ref(0)
provide('count', count)  // 提供响应式数据
provide('increment', () => count.value++)
javascript 复制代码
// Vue 3 后代组件
import { inject } from 'vue'

const count = inject('count', 0)  // 第二个参数是默认值
const increment = inject('increment')

四、模板变化

1. 多根节点(Fragment)

html 复制代码
<!-- Vue 2:必须包裹单一根元素 -->
<template>
  <div>
    <header>...</header>
    <main>...</main>
    <footer>...</footer>
  </div>
</template>
html 复制代码
<!-- Vue 3:支持多根节点 -->
<template>
  <header>...</header>
  <main>...</main>
  <footer>...</footer>
</template>

2. Teleport(瞬移组件)

html 复制代码
<!-- Vue 3 新增,解决弹窗层级问题 -->
<template>
  <button @click="show = true">打开弹窗</button>
  <Teleport to="body">
    <div v-if="show" class="modal">
      <p>我渲染到了 body 下</p>
      <button @click="show = false">关闭</button>
    </div>
  </Teleport>
</template>

3. 动态指令参数

html 复制代码
<!-- Vue 3 支持动态指令参数 -->
<script setup>
const propName = ref('title')
const eventName = ref('click')
</script>

<template>
  <h1 :[propName]="dynamicValue">动态绑定 v-bind 参数</h1>
  <button @[eventName]="handler">动态绑定 v-on 参数</button>
</template>

4. 过滤器(Filters)被移除

html 复制代码
<!-- Vue 2 -->
<p>{{ message | capitalize }}</p>
html 复制代码
<!-- Vue 3:使用计算属性或方法替代 -->
<p>{{ capitalize(message) }}</p>
javascript 复制代码
// Vue 3 <script setup>
import { computed, ref } from 'vue'

const message = ref('hello')
const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1)

五、其他破坏性变更

特性 Vue 2 Vue 3
全局 API Vue.component app.component
原型挂载 Vue.prototype.$http app.config.globalProperties.$http
事件总线 Vue.prototype.$bus = new Vue() 移除,推荐 mitt 或 Pinia
$children 支持 移除,用 ref + defineExpose
$listeners 支持 合并到 $attrs
自定义指令钩子 bind, inserted, componentUpdated beforeMount, mounted, updated
keyCode 修饰符 @keyup.13 移除,用 @keyup.enter
.sync 修饰符 支持 移除,用 v-model:xxx

六、响应式原理差异

javascript 复制代码
// Vue 2:Object.defineProperty
// 问题:无法检测对象属性新增/删除,数组索引修改
data() {
  return { user: { name: '张三' } }
}
// 下面这行不会触发视图更新
this.user.age = 25
// 必须用 this.$set(this.user, 'age', 25)
javascript 复制代码
// Vue 3:Proxy
const user = reactive({ name: '张三' })
// 下面这行直接触发视图更新
user.age = 25

七、完整实战:TodoList 组件对照

Vue 2:

vue 复制代码
<template>
  <div>
    <input v-model="newTodo" @keyup.enter="addTodo" />
    <ul>
      <li v-for="todo in todos" :key="todo.id">
        <span :class="{ done: todo.completed }" @click="toggle(todo)">
          {{ todo.text }}
        </span>
        <button @click="remove(todo)">删除</button>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      newTodo: '',
      todos: [
        { id: 1, text: '学习 Vue 2', completed: true }
      ]
    }
  },
  methods: {
    addTodo() {
      if (!this.newTodo.trim()) return
      this.todos.push({
        id: Date.now(),
        text: this.newTodo,
        completed: false
      })
      this.newTodo = ''
    },
    toggle(todo) {
      todo.completed = !todo.completed
    },
    remove(todo) {
      this.todos = this.todos.filter(t => t.id !== todo.id)
    }
  }
}
</script>

Vue 3(<script setup>):

vue 复制代码
<template>
  <div>
    <input v-model="newTodo" @keyup.enter="addTodo" />
    <ul>
      <li v-for="todo in todos" :key="todo.id">
        <span :class="{ done: todo.completed }" @click="toggle(todo)">
          {{ todo.text }}
        </span>
        <button @click="remove(todo)">删除</button>
      </li>
    </ul>
  </div>
</template>

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

const newTodo = ref('')
const todos = ref([
  { id: 1, text: '学习 Vue 3', completed: true }
])

const addTodo = () => {
  if (!newTodo.value.trim()) return
  todos.value.push({
    id: Date.now(),
    text: newTodo.value,
    completed: false
  })
  newTodo.value = ''
}

const toggle = (todo) => {
  todo.completed = !todo.completed
}

const remove = (todo) => {
  todos.value = todos.value.filter(t => t.id !== todo.id)
}
</script>

八、迁移建议

💡 Vue 2 已于 2023 年 12 月 31 日停止维护 ,新项目直接用 Vue 3 + Vite + <script setup>。

老项目迁移路径:

  1. 先升到 Vue 2.7 :支持 Composition API 和 <script setup>(兼容模式)
  2. 使用 @vue/compat 迁移构建:在 Vue 3 中兼容大部分 Vue 2 API,并给出弃用警告
  3. 逐组件迁移:新功能用 Composition API,旧组件逐步改写
  4. 替换全局 API :Vue.component → app.component 等
  5. 移除废弃特性 :过滤器、$children、.sync 修饰符等
  6. 升级构建工具:从 Vue CLI(Webpack)迁移到 Vite

关键提醒:

  • <script setup> 中 ref 必须加 .value(模板中不需要)
  • reactive 对象解构要用 toRefs 保持响应式
  • 生命周期钩子需要从 vue 导入
  • v-model 默认 prop 从 value 改为 modelValue

九、核心区别速记

维度 Vue 2 Vue 3
API 风格 Options API Composition API + <script setup>
响应式 Object.defineProperty Proxy
入口 new Vue() createApp()
根节点 单一根节点 支持多根节点(Fragment)
v-model :value + @input :modelValue + @update:modelValue
过滤器 支持 移除,用 computed/method
TS 支持 需装饰器,支持较弱 原生 TypeScript 重写,类型友好
性能 基准 更小、更快、Tree-shaking 友好
新组件 无 Teleport、Suspense

Vue 3 不是简单的版本升级,而是开发范式的转变------从"按选项分类"到"按逻辑聚合" 。刚开始写 Vue 3 可能会不习惯 .value,但一旦适应 Composition API,你会发现复杂组件的代码组织能力有了质的飞跃。

相关推荐
码云之上13 分钟前
10 颗一模一样的「Edit」:中后台 Web Agent 怎么找到对的那一行
前端·人工智能·浏览器
量化分析码农41 分钟前
【Python量化多因子实战 04】因子暴露不是 alpha:市值与行业中性化实战
前端·后端
不可能片场1 小时前
Electron 发布 正文写入的三条通道与读回口径
前端·electron
程序员大阳1 小时前
副队长JavaScript教程(66)-本地存储实战:待办事项网页的开发
javascript·json·实战·待办事项·本地存储·案例
jerrywus1 小时前
让 AI 帮你部署 Vercel,报错了也不用手动搬日志
前端·agent·mcp
补码缩写2 小时前
11 行 Python 拆 PNG 块,解压后的字节数能手算
前端
coding漫漫长路2 小时前
PNG压缩先看图是哪一类,先无损再量化多半白做
前端
用户257680763212 小时前
搜「洗牙」为什么搜不到「洁治」
前端
费曼学习法2 小时前
Agent 死循环的根因不在模型:错误分类、退避、熔断、快照四层实现
前端