Vue 2 与 Vue 3 语法区别完整对比

300 阅读4分钟

大家好,我是鱼樱!!!

关注公众号【鱼樱AI实验室】持续每天分享更多前端和AI辅助前端编码新知识~~喜欢的就一起学反正开源至上,无所谓被诋毁被喷被质疑文章没有价值~

一个城市淘汰的自由职业-农村前端程序员(虽然不靠代码挣钱,写文章就是为爱发电),兼职远程上班目前!!!热心坚持分享多数人疑惑的到底vue2/vue3差异语法有哪些~~~

1. 核心 API 的变化

实例创建

Vue 2:

 
import Vue from 'vue'
const app = new Vue({
  el: '#app',
  // 选项...
})

Vue 3:

 
import { createApp } from 'vue'
const app = createApp({
  // 选项...
})
app.mount('#app')

全局 API

Vue 2:

 
// 全局注册组件
Vue.component('my-component', {
  // 选项...
})

// 全局指令
Vue.directive('my-directive', {
  // 钩子函数...
})

// 全局混入
Vue.mixin({
  // 选项...
})

// 全局使用插件
Vue.use(MyPlugin)

Vue 3:

 
// 应用实例注册组件
app.component('my-component', {
  // 选项...
})

// 应用实例注册指令
app.directive('my-directive', {
  // 钩子函数...
})

// 应用实例混入
app.mixin({
  // 选项...
})

// 应用实例使用插件
app.use(MyPlugin)

2. 组合式 API(Composition API)

基本用法

Vue 2:

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

Vue 3:

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

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

function increment() {
  count.value++
}

onMounted(() => {
  console.log('组件已挂载')
})
</script>

响应式系统

Vue 2:

 
export default {
  data() {
    return {
      user: {
        name: 'John',
        age: 30
      }
    }
  },
  methods: {
    updateUser() {
      // 直接修改对象属性是响应式的
      this.user.name = 'Jane'
      
      // 添加新属性需要 Vue.set 才能保持响应式
      this.$set(this.user, 'address', 'New York')
    }
  }
}

Vue 3:

 
import { reactive } from 'vue'

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

// 直接修改对象属性是响应式的
user.name = 'Jane'
// 直接添加新属性也是响应式的
user.address = 'New York'

3. script setup 语法糖

Vue 3 独有:

 
<script setup>
import { ref } from 'vue'
import MyComponent from './MyComponent.vue'

// 导入的组件自动注册
// props 和 emits
defineProps({
  title: String
})

defineEmits(['update', 'delete'])

// 暴露给父组件的公共属性和方法
defineExpose({
  someMethod() {
    // ...
  }
})

// 局部变量和函数自动可用于模板
const count = ref(0)
function increment() {
  count.value++
}
</script>

<template>
  <div>
    <MyComponent />
    <button @click="increment">{{ count }}</button>
  </div>
</template>

4. 生命周期钩子

命名变化

Vue 2Vue 3
beforeCreatesetup()
createdsetup()
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeDestroyonBeforeUnmount
destroyedonUnmounted
errorCapturedonErrorCaptured
-onRenderTracked
-onRenderTriggered
-onActivated
-onDeactivated
-onServerPrefetch

使用对比

Vue 2:

 
export default {
  created() {
    console.log('组件创建')
  },
  mounted() {
    console.log('组件挂载')
  },
  beforeDestroy() {
    console.log('组件即将销毁')
  }
}

Vue 3 选项式 API:

 
export default {
  created() {
    console.log('组件创建')
  },
  mounted() {
    console.log('组件挂载')
  },
  beforeUnmount() { // 注意名称变化
    console.log('组件即将卸载')
  }
}

Vue 3 组合式 API:

 
import { onMounted, onBeforeUnmount } from 'vue'

export default {
  setup() {
    // created 钩子的代码直接在 setup 中执行
    console.log('组件创建')
    
    onMounted(() => {
      console.log('组件挂载')
    })
    
    onBeforeUnmount(() => {
      console.log('组件即将卸载')
    })
  }
}

5. 多根节点组件 (Fragment)

Vue 2: 必须有一个单根节点

 
<template>
  <div>
    <header>...</header>
    <main>...</main>
    <footer>...</footer>
  </div>
</template>

Vue 3: 支持多根节点

 
<template>
  <header>...</header>
  <main>...</main>
  <footer>...</footer>
</template>

6. Teleport 组件

Vue 3 独有:

 
<template>
  <div>
    <!-- 正常内容 -->
    
    <!-- 传送到 body 末尾 -->
    <teleport to="body">
      <div class="modal">
        <!-- 模态框内容 -->
      </div>
    </teleport>
  </div>
</template>

7. 自定义指令

Vue 2:

 
Vue.directive('highlight', {
  bind(el, binding) {
    el.style.backgroundColor = binding.value
  },
  inserted(el) {
    // ...
  },
  update(el, binding) {
    el.style.backgroundColor = binding.value
  },
  componentUpdated() {
    // ...
  },
  unbind() {
    // ...
  }
})

Vue 3:

 
app.directive('highlight', {
  // 与 Vue 2 相比钩子函数被重命名
  beforeMount(el, binding) {
    el.style.backgroundColor = binding.value
  },
  mounted() {
    // ...
  },
  beforeUpdate() {
    // ...
  },
  updated(el, binding) {
    el.style.backgroundColor = binding.value
  },
  beforeUnmount() {
    // ...
  },
  unmounted() {
    // ...
  }
})

8. 事件 API

Vue 2:

 
// 事件总线
Vue.prototype.$bus = new Vue()

// 组件 A
this.$bus.$emit('custom-event', payload)

// 组件 B
created() {
  this.$bus.$on('custom-event', this.handleEvent)
},
beforeDestroy() {
  this.$bus.$off('custom-event', this.handleEvent)
}

Vue 3: 移除了事件总线,推荐使用外部库或 mitt/tiny-emitter

 
// 安装: npm install mitt
import mitt from 'mitt'
const emitter = mitt()

// 创建全局实例
app.config.globalProperties.emitter = emitter

// 组件 A
emitter.emit('custom-event', payload)

// 组件 B
import { getCurrentInstance, onUnmounted } from 'vue'

setup() {
  const { proxy } = getCurrentInstance()
  
  const handleEvent = (payload) => {
    // ...
  }
  
  proxy.emitter.on('custom-event', handleEvent)
  
  onUnmounted(() => {
    proxy.emitter.off('custom-event', handleEvent)
  })
}

9. 过滤器

Vue 2:

 
<template>
  <div>{{ price | formatPrice }}</div>
</template>

<script>
export default {
  filters: {
    formatPrice(value) {
      return '¥' + value.toFixed(2)
    }
  }
}
</script>

Vue 3: 过滤器已被移除,推荐使用方法或计算属性

 
<template>
  <div>{{ formatPrice(price) }}</div>
</template>

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

const price = ref(100)

function formatPrice(value) {
  return '¥' + value.toFixed(2)
}
</script>

10. 异步组件

Vue 2:

 
// 全局注册
Vue.component('async-component', () => import('./AsyncComponent.vue'))

// 局部注册
export default {
  components: {
    AsyncComponent: () => import('./AsyncComponent.vue')
  }
}

Vue 3:

 
import { defineAsyncComponent } from 'vue'

// 基本用法
const AsyncComp = defineAsyncComponent(() => 
  import('./AsyncComponent.vue')
)

// 高级用法
const AsyncCompWithOptions = defineAsyncComponent({
  loader: () => import('./AsyncComponent.vue'),
  loadingComponent: LoadingComponent,
  errorComponent: ErrorComponent,
  delay: 200,
  timeout: 3000
})

11. 响应式引用

Vue 2:

 
export default {
  data() {
    return {
      count: 0,
      user: { name: 'John' }
    }
  },
  methods: {
    updateUser() {
      // 直接修改嵌套对象属性
      this.user.name = 'Jane'
      
      // 替换整个对象
      this.user = { name: 'Mike' }
    }
  }
}

Vue 3:

 
import { ref, reactive } from 'vue'

// 基本类型使用 ref
const count = ref(0)
// 访问和修改需要 .value
console.log(count.value)
count.value++

// 对象使用 reactive
const user = reactive({ name: 'John' })
// 直接访问和修改,不需要 .value
console.log(user.name)
user.name = 'Jane'

12. 组件间通信

Props 和事件

Vue 2:

 
<!-- 父组件 -->
<template>
  <child-component 
    :message="message" 
    @update="handleUpdate"
  />
</template>

<script>
export default {
  data() {
    return {
      message: 'Hello'
    }
  },
  methods: {
    handleUpdate(value) {
      this.message = value
    }
  }
}
</script>

<!-- 子组件 -->
<script>
export default {
  props: {
    message: String
  },
  methods: {
    updateMessage(value) {
      this.$emit('update', value)
    }
  }
}
</script>

Vue 3:

 
<!-- 父组件 -->
<template>
  <child-component 
    :message="message" 
    @update="handleUpdate"
  />
</template>

<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'

const message = ref('Hello')
const handleUpdate = (value) => {
  message.value = value
}
</script>

<!-- 子组件 -->
<script setup>
const props = defineProps({
  message: String
})

const emit = defineEmits(['update'])

function updateMessage(value) {
  emit('update', value)
}
</script>

Provide/Inject

Vue 2:

 
// 父组件
export default {
  provide() {
    return {
      theme: 'dark',
      user: this.user // 注意: 非响应式传递
    }
  },
  data() {
    return {
      user: { name: 'John' }
    }
  }
}

// 子组件
export default {
  inject: ['theme', 'user']
}

Vue 3:

 
// 父组件
import { provide, ref, reactive } from 'vue'

export default {
  setup() {
    const theme = ref('dark')
    const user = reactive({ name: 'John' })
    
    provide('theme', theme) // 响应式传递
    provide('user', user)  // 响应式传递
  }
}

// 子组件
import { inject } from 'vue'

export default {
  setup() {
    const theme = inject('theme')
    const user = inject('user')
    
    // theme.value 可以访问传递的值
    // user.name 直接访问
  }
}

13. 渲染函数和JSX

Vue 2:

 
export default {
  render(h) {
    return h('div', {
      attrs: {
        id: 'app'
      },
      on: {
        click: this.handleClick
      }
    }, [
      h('span', 'Hello Vue 2')
    ])
  },
  methods: {
    handleClick() {
      console.log('clicked')
    }
  }
}

Vue 3:

 
import { h } from 'vue'

export default {
  setup() {
    const handleClick = () => {
      console.log('clicked')
    }
    
    return () => h('div', {
      id: 'app',
      onClick: handleClick
    }, [
      h('span', 'Hello Vue 3')
    ])
  }
}

14. v-model 变化

Vue 2:

 
<!-- 在组件上使用 v-model -->
<custom-input v-model="searchText"></custom-input>

<!-- 等价于 -->
<custom-input
  :value="searchText"
  @input="searchText = $event"
></custom-input>

<!-- CustomInput.vue -->
<script>
export default {
  props: {
    value: String
  },
  methods: {
    updateValue(e) {
      this.$emit('input', e.target.value)
    }
  }
}
</script>

Vue 3:

 
<!-- 在组件上使用 v-model -->
<custom-input v-model="searchText"></custom-input>

<!-- 等价于 -->
<custom-input
  :modelValue="searchText"
  @update:modelValue="searchText = $event"
></custom-input>

<!-- 多个 v-model -->
<CustomInput
  v-model:text="searchText"
  v-model:active="isActive"
/>

<!-- CustomInput.vue -->
<script setup>
const props = defineProps({
  modelValue: String
})

const emit = defineEmits(['update:modelValue'])

function updateValue(e) {
  emit('update:modelValue', e.target.value)
}
</script>

15. Suspense 组件(Vue 3 新增)

 
<template>
  <Suspense>
    <template #default>
      <async-component />
    </template>
    <template #fallback>
      <div>Loading...</div>
    </template>
  </Suspense>
</template>

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

const AsyncComponent = defineAsyncComponent(() => import('./AsyncComponent.vue'))
</script>

16. 组合式函数 (Composables)

Vue 3 独有的模式:

 
// useCounter.js
import { ref, computed } from 'vue'

export function useCounter(initialValue = 0) {
  const count = ref(initialValue)
  const double = computed(() => count.value * 2)
  
  function increment() {
    count.value++
  }
  
  return {
    count,
    double,
    increment
  }
}

// 在组件中使用
import { useCounter } from './useCounter'

export default {
  setup() {
    const { count, double, increment } = useCounter(10)
    
    return {
      count,
      double,
      increment
    }
  }
}

// 在 script setup 中使用
import { useCounter } from './useCounter'

const { count, double, increment } = useCounter(10)

17. TypeScript 支持

Vue 3 相较于 Vue 2 有更强的 TypeScript 支持:

 
// Vue 3 中使用 TypeScript 的 defineComponent
import { defineComponent, ref, PropType } from 'vue'

interface User {
  id: number
  name: string
}

export default defineComponent({
  props: {
    user: {
      type: Object as PropType<User>,
      required: true
    }
  },
  setup(props) {
    const count = ref<number>(0)
    return { count }
  }
})

// script setup 中使用 TypeScript
<script setup lang="ts">
import { ref } from 'vue'

interface User {
  id: number
  name: string
}

const props = defineProps<{
  user: User
  optional?: string
}>()

const emit = defineEmits<{
  (e: 'change', id: number): void
  (e: 'update', value: string): void
}>()

// 带默认值的 props
withDefaults(defineProps<{
  size?: number
  message?: string
}>(), {
  size: 0,
  message: 'hello'
})
</script>

18. 主要破坏性变更

  1. v-if 与 v-for 优先级变更

    • Vue 2: v-for 优先于 v-if
    • Vue 3: v-if 优先于 v-for
  2. 全局 API 不再污染全局空间

  3. 移除了 Vue.set()/Vue.delete()this.delete()

  4. 移除了过滤器 (filters)

  5. 移除了 off, $once 事件 API

  6. 移除了 $children API

  7. 移除了 .native 修饰符

    • Vue 2: <my-component @click.native="onClick" />
    • Vue 3: 在组件中使用 emits 选项声明所有事件

19. 总结对比

特性Vue 2Vue 3
核心架构基于选项式 API支持组合式 API 和选项式 API
全局 API直接修改 Vue,全局污染应用实例 API,不污染全局
响应式系统Object.definePropertyProxy 实现,性能更好
组件根节点必须有单一根节点支持多根节点
TypeScript 支持有限完全支持,内置类型
渲染性能较好显著提升,静态树提升等优化
包体积较大更小,支持 tree-shaking
setup 函数不支持新增,更好的逻辑组织方式
v-model使用 value/input使用 modelValue/update:modelValue
自定义指令 API生命周期钩子名基于组件生命周期钩子名统一
异步组件简单函数使用 defineAsyncComponent
过滤器支持移除
事件总线内置移除,推荐使用外部库

Vue 3 相比 Vue 2 带来了更好的性能、更灵活的 API 以及更好的 TypeScript 支持,同时保留了 Vue 2 的易用性,为开发提供了更多选择。