Vue 2 to Vue 3 Migration Guide: A Smooth Transition
Vue 2 to Vue 3 Migration Guide: A Smooth Transition
Migrating from Vue 2 to Vue 3 requires understanding the breaking changes and new features. This guide will help you navigate the transition smoothly.
Breaking Changes Overview
Global API Changes
Vue 3 introduces a new global API:
// Vue 2
import Vue from 'vue';
import App from './App.vue';
Vue.config.productionTip = false;
Vue.use(VueRouter);
Vue.component('my-component', {});
new Vue({
router,
render: h => h(App)
}).$mount('#app');
// Vue 3
import { createApp } from 'vue';
import App from './App.vue';
const app = createApp(App);
app.use(router);
app.component('my-component', {});
app.mount('#app');
Multiple App Instances
// Vue 3 allows multiple independent apps
const app1 = createApp(App1);
app1.mount('#app1');
const app2 = createApp(App2);
app2.mount('#app2');
// Each app has its own configuration
app1.config.errorHandler = (err) => console.log('App 1:', err);
app2.config.errorHandler = (err) => console.log('App 2:', err);
v-model Changes
Vue 3 unifies v-model syntax:
<!-- Vue 2 -->
<CustomInput v-model="value" />
<!-- Vue 2 component -->
<input :value="value" @input="$emit('input', $event.target.value)" />
<!-- Vue 3 -->
<CustomInput v-model="value" />
<CustomInput v-model:title="title" />
<!-- Vue 3 component -->
<input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" />
<input :value="title" @input="$emit('update:title', $event.target.value)" />
Multiple v-models
<!-- Vue 3 supports multiple v-models -->
<UserForm
v-model:firstName="firstName"
v-model:lastName="lastName"
/>
<script>
export default {
props: {
firstName: String,
lastName: String
},
emits: ['update:firstName', 'update:lastName']
}
</script>
<template>
<input
:value="firstName"
@input="$emit('update:firstName', $event.target.value)"
/>
<input
:value="lastName"
@input="$emit('update:lastName', $event.target.value)"
/>
</template>
v-if and v-for Priority
<!-- Vue 2: v-for has higher priority -->
<!-- Vue 3: v-if has higher priority -->
<!-- This won't work in Vue 3 -->
<div v-for="item in items" v-if="item.isActive">
{{ item.name }}
</div>
<!-- Solution 1: Use computed -->
<div v-for="item in activeItems">
{{ item.name }}
</div>
<!-- Solution 2: Use wrapper -->
<template v-for="item in items" :key="item.id">
<div v-if="item.isActive">
{{ item.name }}
</div>
</template>
Lifecycle Hooks
Hook names have changed slightly:
// Vue 2 // Vue 3
beforeCreate → beforeCreate
created → created
beforeMount → onBeforeMount // was beforeMount
mounted → onMounted
beforeUpdate → onBeforeUpdate
updated → onUpdated
beforeDestroy → onBeforeUnmount // renamed!
destroyed → onUnmounted // renamed!
errorCaptured → onErrorCaptured
Filters Removed
Vue 3 removes filters:
<!-- Vue 2 -->
<p>{{ message | capitalize }}</p>
<!-- Vue 3: Use methods or computed -->
<p>{{ capitalize(message) }}</p>
<p>{{ capitalizedMessage }}</p>
<script>
export default {
computed: {
capitalizedMessage() {
return this.message.charAt(0).toUpperCase() + this.message.slice(1);
}
},
methods: {
capitalize(value) {
return value.charAt(0).toUpperCase() + value.slice(1);
}
}
}
</script>
<!-- Or use global method -->
app.config.globalProperties.$filters = {
capitalize(value) {
return value.charAt(0).toUpperCase() + value.slice(1);
}
};
Teleport Component
Vue 3 introduces Teleport for rendering outside component hierarchy:
<template>
<button @click="showModal = true">Open Modal</button>
<Teleport to="body">
<div v-if="showModal" class="modal">
<p>Modal content</p>
<button @click="showModal = false">Close</button>
</div>
</Teleport>
</template>
Fragments
Vue 3 supports multiple root elements:
<!-- Vue 2: Single root required -->
<template>
<div>
<h1>Title</h1>
<p>Content</p>
</div>
</template>
<!-- Vue 3: Multiple roots allowed -->
<template>
<h1>Title</h1>
<p>Content</p>
</template>
emits Option
Declare emitted events explicitly:
<script>
export default {
emits: ['update:modelValue', 'submit'],
// With validation
emits: {
'update:modelValue': (value) => typeof value === 'string',
'submit': (payload) => {
return payload.email && payload.password;
}
}
}
</script>
Migration Build
Use the migration build for gradual migration:
// vue.config.js (Vue CLI)
module.exports = {
chainWebpack: config => {
config.resolve.alias.set('vue', '@vue/compat')
}
}
// vite.config.js
export default {
resolve: {
alias: {
vue: '@vue/compat'
}
}
}
Enable compatibility features:
app.config.compilerOptions = {
compatConfig: {
MODE: 2,
FILTERS: true,
GLOBAL_MOUNT: true
}
}
Composition API in Options API
You can use Composition API features in Vue 2 with @vue/composition-api:
// Vue 2 with plugin
import VueCompositionAPI from '@vue/composition-api';
Vue.use(VueCompositionAPI);
// Then use setup()
export default {
setup() {
const count = ref(0);
return { count };
}
}
Store Migration (Vuex)
// Vuex 3 (Vue 2)
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: {},
mutations: {},
actions: {}
});
// Vuex 4 (Vue 3)
import { createStore } from 'vuex';
export default createStore({
state: {},
mutations: {},
actions: {}
});
// In main.js
import store from './store';
app.use(store);
Router Migration (Vue Router)
// Vue Router 3 (Vue 2)
import VueRouter from 'vue-router';
Vue.use(VueRouter);
export default new VueRouter({
routes: []
});
// Vue Router 4 (Vue 3)
import { createRouter, createWebHistory } from 'vue-router';
const router = createRouter({
history: createWebHistory(),
routes: []
});
export default router;
Removed APIs
| Vue 2 | Vue 3 Alternative |
|---|---|
$on, $off, $once | Use external library like mitt |
| Filters | Methods/computed |
$children | Use refs or provide/inject |
$listeners | Merged into $attrs |
$scopedSlots | Unified as $slots |
data as object | Always function |
Migration Checklist
- Update dependencies - Vue, Vue Router, Vuex, tooling
- Check breaking changes - Global API, v-model, filters
- Update build config - Webpack/Vite configuration
- Fix deprecation warnings - Use migration build
- Test thoroughly - Unit tests, E2E tests
- Gradual migration - Use migration build for large apps
- Remove compat mode - Once fully migrated
Quick Migration Script
# Install migration build
npm install @vue/compat
# Or upgrade all at once
npm install vue@next vue-router@4 vuex@4
# Use upgrade tool
npx @vue/upgrade next
Conclusion
Migrating to Vue 3 brings significant improvements in performance, TypeScript support, and developer experience. Use the migration build for gradual upgrades, and refer to the official migration guide for detailed information on each breaking change.