Upgrade/Migrate your Vue 2.x projects to Vue 3.0
With the release of vue-next v3.0.0-beta.1
in April 2020, as of today, Vue has reached an official v3.0 release — One Piece. As a fellow Vue junkie, I am feeling the same excitement as any other frontend devs because of the new features and enhancements this would bring. However, that brings the question: “how to upgrade my existing Vue 2 projects to Vue 3?”.

Taking a look at the release notes, we see that Vue 3 brings changes to the core library as well as the Vue ecosystem:
- internal API exposed as modular packages, e.g.
compiler
,reactivity
,runtime
- Composition API
- Performance Up: bundle size (up to 41% lighter with tree-shaking), initial render (up to 55% faster), updates (up to 133% faster), and memory usage (up to 54% less)
- Fully supports Typescript (TSX)
- Experimental:
<script setup>
: syntactic sugar for using Composition API inside SFCs - Experimental:
<style vars>
: state-driven CSS variables inside SFCs
We’ll be looking at how to scaffold Vue 3 projects based on existing Vue 2 code, and show deployable code than sandboxed tutorials out there. A brief outline here:
- Setting up
- Writing Components
- Integrate
vue-router (v4.x)
- Integrate
vuex (v4.x)
Setup Syntax
In Vue 2.x you would set up with the following:
import Vue from 'vue'
import App from './App.vue'new Vue({
render: (h) => h(App)
}).$mount(“#app”);
We are using the global Vue object to create a Vue instance. Any change made to this Vue object will affect every Vue instance and component.
In Vue 3, a new function createApp
is provided so now every configuration is scoped to a certain Vue application defined:
import { createApp } from 'vue'
import App from './App.vue'const app = createApp(App)
app.mount('#app')
Back in Vue 2.x, if a third party library is modifying the Vue object, it creates unexpected changes (especially with global mixins) which would not be the case with Vue 3.
Additionally, you can use these methods in app
: config
, use
, mixin
, component
, directive
, mount
, unmount
, provide
/inject
. For more info, refer to the official docs: Application API.
Writing Components
In Vue 3, the component construction hasn’t changed much. Just that computed
and watch
are extracted out as functions. See below:
<template>
<div class="test">
<h1>test count: {{count}}</h1>
<div>count * 2 = {{doubleCount}}</div>
<button @click="add">add</button>
</div>
</template><script>
import { ref, computed, watch } from 'vue'export default {
setup () {
const count = ref(0)
const add = () => {
count.value++
}
watch(() => count.value, val => {
console.log(`count is ${val}`)
})
const doubleCount = computed(() => count.value * 2)
return {
count,
doubleCount,
add
}
}
}
</script>
Both methods can now be imported and used as needed. Rather than having a function wrapper in Vue 2.x.
Integrate Vue Router (v4.x)
Edit the filesrc/router/index.js
like below:
import { createRouter, createWebHashHistory } from 'vue-router'
import Home from '../views/Home.vue'const routes = [
{
path: '/',
name: 'Home',
component: Home
}
]const router = createRouter({
history: createWebHashHistory(),
routes
})export default router
In Vue 3, initializing the router is not much different to Vue 2.x. Only that the function construction is taken over by the new createRouter
.
To get the current route within a component, you can do the following:
import { getCurrentInstance } from 'vue'export default {
setup () {
const { ctx } = getCurrentInstance()
console.log(ctx.$router.currentRoute.value)
}
}
Integrate Vuex (v4.x)
Edit the filesrc/store/index.js
like below:
import Vuex from 'vuex'export default Vuex.createStore({
state: {
counter: {
val: 1
}
},
mutations: {
setCounterVal(state, value) {
state.counter.val = value
}
},
actions: {
},
modules: {
}
})
The syntax hasn’t changed much here. We added counter.val
to the state and added a mutation to mutate counter.val
— setCounterVal
.
In the component, we can reference the vuex state using computed
, and ctx.$store
to access the mutation methods.
<template>
<div class="test">
<h1>test count: {{count}}</h1>
<div>count * 2 = {{doubleCount}}</div>
<div>state from vuex {{a}}</div>
<button @click="add">add</button>
</div>
</template><script>
import { ref, computed, watch, getCurrentInstance } from 'vue'export default {
setup () {
const count = ref(0)
const add = () => {
count.value++
}
watch(() => count.value, val => {
console.log(`count is ${val}`)
}) const doubleCount = computed(() => count.value * 2)
const { ctx } = getCurrentInstance()
console.log(ctx.$router.currentRoute.value)
const a = computed(() => ctx.$store.state.counter.val)
const update = () => {
ctx.$store.commit('setCountVal', count)
}
return {
count,
doubleCount,
add,
a,
update
}
}
}
</script>
Like Vue 2.x, we would still use $store.commit
to call the vuex mutation setCountVal
.
Remember to return all the relevant functions or variables at the end of the component.
Summary
Apart from createApp, Vue Router and Vuex upgrades, we are also welcomed to the new Composition API. We can see Vue is going towards better APIs and simpler development workflow. The core Vue team has adopted many ideas from third party libraries and integrated them to the official framework.
We’ve only touched on the major API changes and improvements. To view the full list, do check out the Vue RFCs repo.