大家好,我是鱼樱!!!
关注公众号【鱼樱AI实验室】
持续每天分享更多前端和AI辅助前端编码新知识~~喜欢的就一起学反正开源至上,无所谓被诋毁被喷被质疑文章没有价值~
一个城市淘汰的自由职业-农村前端程序员(虽然不靠代码挣钱,写文章就是为爱发电),兼职远程上班目前!!!热心坚持分享多数人疑惑的到底vue2/vue3差异语法
有哪些~~~
1. 核心 API 的变化
实例创建
Vue 2:
js
import Vue from 'vue'
const app = new Vue({
el: '#app',
// 选项...
})
Vue 3:
js
import { createApp } from 'vue'
const app = createApp({
// 选项...
})
app.mount('#app')
全局 API
Vue 2:
js
// 全局注册组件
Vue.component('my-component', {
// 选项...
})
// 全局指令
Vue.directive('my-directive', {
// 钩子函数...
})
// 全局混入
Vue.mixin({
// 选项...
})
// 全局使用插件
Vue.use(MyPlugin)
Vue 3:
js
// 应用实例注册组件
app.component('my-component', {
// 选项...
})
// 应用实例注册指令
app.directive('my-directive', {
// 钩子函数...
})
// 应用实例混入
app.mixin({
// 选项...
})
// 应用实例使用插件
app.use(MyPlugin)
2. 组合式 API(Composition API)
基本用法
Vue 2:
js
<script>
export default {
data() {
return {
count: 0
}
},
methods: {
increment() {
this.count++
}
},
computed: {
doubleCount() {
return this.count * 2
}
},
mounted() {
console.log('组件已挂载')
}
}
</script>
Vue 3:
js
<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:
js
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:
js
import { reactive } from 'vue'
const user = reactive({
name: 'John',
age: 30
})
// 直接修改对象属性是响应式的
user.name = 'Jane'
// 直接添加新属性也是响应式的
user.address = 'New York'
3. script setup 语法糖
Vue 3 独有:
js
<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 2 | Vue 3 |
---|---|
beforeCreate | setup() |
created | setup() |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeDestroy | onBeforeUnmount |
destroyed | onUnmounted |
errorCaptured | onErrorCaptured |
- | onRenderTracked |
- | onRenderTriggered |
- | onActivated |
- | onDeactivated |
- | onServerPrefetch |
使用对比
Vue 2:
js
export default {
created() {
console.log('组件创建')
},
mounted() {
console.log('组件挂载')
},
beforeDestroy() {
console.log('组件即将销毁')
}
}
Vue 3 选项式 API:
js
export default {
created() {
console.log('组件创建')
},
mounted() {
console.log('组件挂载')
},
beforeUnmount() { // 注意名称变化
console.log('组件即将卸载')
}
}
Vue 3 组合式 API:
js
import { onMounted, onBeforeUnmount } from 'vue'
export default {
setup() {
// created 钩子的代码直接在 setup 中执行
console.log('组件创建')
onMounted(() => {
console.log('组件挂载')
})
onBeforeUnmount(() => {
console.log('组件即将卸载')
})
}
}
5. 多根节点组件 (Fragment)
Vue 2: 必须有一个单根节点
html
<template>
<div>
<header>...</header>
<main>...</main>
<footer>...</footer>
</div>
</template>
Vue 3: 支持多根节点
html
<template>
<header>...</header>
<main>...</main>
<footer>...</footer>
</template>
6. Teleport 组件
Vue 3 独有:
html
<template>
<div>
<!-- 正常内容 -->
<!-- 传送到 body 末尾 -->
<teleport to="body">
<div class="modal">
<!-- 模态框内容 -->
</div>
</teleport>
</div>
</template>
7. 自定义指令
Vue 2:
js
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:
js
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:
js
// 事件总线
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
js
// 安装: 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:
html
<template>
<div>{{ price | formatPrice }}</div>
</template>
<script>
export default {
filters: {
formatPrice(value) {
return '¥' + value.toFixed(2)
}
}
}
</script>
Vue 3: 过滤器已被移除,推荐使用方法或计算属性
html
<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:
js
// 全局注册
Vue.component('async-component', () => import('./AsyncComponent.vue'))
// 局部注册
export default {
components: {
AsyncComponent: () => import('./AsyncComponent.vue')
}
}
Vue 3:
js
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:
js
export default {
data() {
return {
count: 0,
user: { name: 'John' }
}
},
methods: {
updateUser() {
// 直接修改嵌套对象属性
this.user.name = 'Jane'
// 替换整个对象
this.user = { name: 'Mike' }
}
}
}
Vue 3:
js
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:
html
<!-- 父组件 -->
<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:
html
<!-- 父组件 -->
<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:
js
// 父组件
export default {
provide() {
return {
theme: 'dark',
user: this.user // 注意: 非响应式传递
}
},
data() {
return {
user: { name: 'John' }
}
}
}
// 子组件
export default {
inject: ['theme', 'user']
}
Vue 3:
js
// 父组件
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:
js
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:
js
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:
js
<!-- 在组件上使用 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:
js
<!-- 在组件上使用 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 新增)
html
<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 独有的模式:
js
// 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 支持:
js
// 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. 主要破坏性变更
-
v-if 与 v-for 优先级变更
- Vue 2: v-for 优先于 v-if
- Vue 3: v-if 优先于 v-for
-
全局 API 不再污染全局空间
-
移除了 Vue.set()/Vue.delete() 和 this.delete()
-
移除了过滤器 (filters)
-
移除了 off, $once 事件 API
-
移除了 $children API
-
移除了 .native 修饰符
- Vue 2:
<my-component @click.native="onClick" />
- Vue 3: 在组件中使用
emits
选项声明所有事件
- Vue 2:
19. 总结对比
特性 | Vue 2 | Vue 3 |
---|---|---|
核心架构 | 基于选项式 API | 支持组合式 API 和选项式 API |
全局 API | 直接修改 Vue,全局污染 | 应用实例 API,不污染全局 |
响应式系统 | Object.defineProperty | Proxy 实现,性能更好 |
组件根节点 | 必须有单一根节点 | 支持多根节点 |
TypeScript 支持 | 有限 | 完全支持,内置类型 |
渲染性能 | 较好 | 显著提升,静态树提升等优化 |
包体积 | 较大 | 更小,支持 tree-shaking |
setup 函数 | 不支持 | 新增,更好的逻辑组织方式 |
v-model | 使用 value/input | 使用 modelValue/update:modelValue |
自定义指令 API | 生命周期钩子名基于组件 | 生命周期钩子名统一 |
异步组件 | 简单函数 | 使用 defineAsyncComponent |
过滤器 | 支持 | 移除 |
事件总线 | 内置 | 移除,推荐使用外部库 |
Vue 3 相比 Vue 2 带来了更好的性能、更灵活的 API 以及更好的 TypeScript 支持,同时保留了 Vue 2 的易用性,为开发提供了更多选择。