Migrating from Vue 2 to Vue 3 can be a complex task, but with the right knowledge and approach, it doesn’t have to be overwhelming. In this guide, we will explore the top tips and best practices for a successful migration from Vue 2 to Vue 3. Whether you are a seasoned Vue developer or new to the framework, this article will provide you with the essential information you need to smoothly transition your Vue 2 project to Vue 3.
Why Upgrade to Vue 3?
Before we dive into the nitty-gritty of the migration process, let’s talk about why you should upgrade to Vue 3. Vue 3 comes with a host of new features and improvements that make it faster, smaller, and more efficient than Vue 2. With the new Composition API, reactivity system, and better TypeScript support, Vue 3 is a game-changer for web development.
Useful Tips
main.js
- Options API to Composition API
- Vue Router
Vue.filter
DeprecatedbeforeDestroy
Renamed- Dependencies
- Convert usage of
.native
and$listeners
main.js
In Vue 3, the createApp
function is used to create the application instance, and the mount method is used to mount the app to the DOM element with the id of app. This simplifies the code compared to Vue 2, where we had to create a new Vue instance and use the $mount
method to mount the app.
// === Vue 2 === import Vue from 'vue' import App from './App.vue' new Vue({ render: h => h(App), }).$mount('#app') // === Vue 3 === import { createApp } from 'vue' import App from './App.vue' createApp(App).mount('#app')
Options API to Composition API
In Vue 3, you have two options for defining the composition logic of a component: using the setup()
function or the <script setup>
syntax.
<!-- === Vue 2 === --> <template> <div> <p>{{ message }}</p> <button @click="updateMessage">Change Message</button> </div> </template> <script> export default { data() { return { message: 'Hello, Vue!' }; }, methods: { updateMessage() { this.message = 'Welcome to Vue 3!'; } } }; </script> <!-- === Vue 3 using <script setup> === --> <template> <div> <p>{{ message }}</p> <button @click="updateMessage">Change Message</button> </div> </template> <script setup> import { ref } from 'vue'; const message = ref('Hello, Vue!'); const updateMessage = () => { message.value = 'Welcome to Vue 3!'; } </script> <!-- === Vue 3 using setup() function === --> <template> <div> <p>{{ message }}</p> <button @click="updateMessage">Change Message</button> </div> </template> <script> import { ref } from 'vue'; export default { setup() { const message = ref('Hello, Vue!'); const updateMessage = () => { message.value = 'Welcome to Vue 3!'; } return { message, updateMessage, }; } }; </script>
Vue Router
// === Vue 2 === import Vue from 'vue'; import VueRouter from 'vue-router'; import Home from './components/Home.vue'; import About from './components/About.vue'; Vue.use(VueRouter); const router = new VueRouter({ routes: [ { path: '/', component: Home }, { path: '/about', component: About } ] }); // === Vue 3 === import { createRouter, createWebHistory } from 'vue-router'; import Home from './components/Home.vue'; import About from './components/About.vue'; const router = createRouter({ // Remove hash (#) from url, as opposed to createWebHashHistory which uses hash history: createWebHistory(), routes: [ { path: '/', component: Home }, { path: '/about', component: About } ] });
Vue.filter
Deprecated
In Vue 3, instead of directly registering filters on the Vue constructor, we create a global filter by adding it to app.config.globalProperties.$filters. This makes the filter available globally in the application. To use the filter in a template, we prepend $filters to the filter name.
// === Vue 2 === // Define a filter Vue.filter('capitalize', function(value) { if (!value) return '' value = value.toString() return value.charAt(0).toUpperCase() + value.slice(1) }) // Use the filter in a template {{ message | capitalize }} // === Vue 3 === import { createApp } from 'vue' // Create a global filter const app = createApp({}) app.config.globalProperties.$filters = { capitalize(value) { if (!value) return '' value = value.toString() return value.charAt(0).toUpperCase() + value.slice(1) } } // Use the filter in a template {{ $filters.capitalize(message) }}
beforeDestroy
Renamed
In Vue 3, the lifecycle hook beforeDestroy
has been renamed to beforeUnmount
. You can import the onBeforeUnmount
function from the vue package and use it inside the setup function to perform any necessary clean-up or actions before the component is unmounted.
// === Vue 2 === export default { // ... beforeDestroy() { // Do something before the component is destroyed }, // ... } // === Vue 3 === import { onBeforeUnmount } from 'vue'; export default { // ... setup() { onBeforeUnmount(() => { // Do something before the component is unmounted }); }, // ... }
Dependencies
- Update all your dependencies to their latest versions, ensuring compatibility with Vue 3.
-
Consider using the
@vue/compat
library to smooth the transition and maintain temporary compatibility with older dependencies.# Install via npm: npm install @vue/compat --save-dev # Install via Yarn: yarn add @vue/compat -D # Install via pnpm: pnpm add @vue/compat -D
Convert usage of .native
and $listeners
-
In parent component, remove
.native
, normally attached to@click
- In child component, make the following adjustments:
// Important: The code below is added to the child component `.vue` file, not `vite.config.js` export default { // `compatConfig` setting should be removed when `@vue/compat` is uninstalled compatConfig: { MODE: 3, }, // `inheritAttrs: false` will prevent root element from inheriting $attrs inheritAttrs: false, ... }
v-bind="$attrs"
to the element that emits event that should be passed to the parent. This should replace v-on="$listeners"
if the attribute exists.
emits: []
array (by default this is not used in Vue2)
v-bind="$attrs"
to one element will resolves warning: [Vue warn]: Extraneous non-props attributes (data-version) were passed to component but could not be automatically inherited because component renders fragment or text root nodes.
@vue/compat
, only need the following (i.e. compatConfig
not needed):
// === Vue 2 === // ParentComponent.vue <template> <ChildComponent @click.native="doSomething"> </template> // ChildComponent.vue <template> <button v-on="$listeners">Click here to do something</button> </template> // === Vue 3 === // ParentComponent.vue <template> <ChildComponent @click="doSomething"> </template> // ChildComponent.vue <template> <button v-bind="$attrs">Click here to do something</button> </template>
Bonus Tips
-
Convert
slot="..."
tov-slot:...
or shorthand#...
-
Convert
@vue/composition-api
tovue
, e.g.import { reactive } from '@vue/composition-api'
toimport { reactive } from 'vue'
-
Change environment variable access, e.g. From
process.env.BASE_URL
toimport.meta.env.BASE_URL
Conclusion
Migrating from Vue 2 to Vue 3 is an exciting opportunity to leverage new features, improve code organization, and enhance the performance of your Vue applications. By following these top tips and best practices, you can navigate the migration process successfully and take full advantage of the power of Vue 3. Remember to plan your migration strategy, understand the changes, and address any breaking changes to ensure a smooth and efficient transition. Happy migrating!