Frontend Development 8 min read

Migrating a Vue2 Project to Vue3 with Pinia and Composition API

This article details a step‑by‑step migration from Vue 2 to Vue 3, emphasizing the use of the latest Vue 3 version, TypeScript typings, replacing mixins with composition functions, adopting Pinia for state management, and demonstrating how to share and protect reactive state with composition APIs.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Migrating a Vue2 Project to Vue3 with Pinia and Composition API

Previously the author used Vue 2 templates in a corporate infrastructure project, mixing Vuex, any types, and both Options API and Composition API. To modernize the business project, they decided to start fresh with Vue 3, applying a minimalist approach.

Key migration steps include:

Using the latest Vue 3 version v3.5.x .

Generating TypeScript typings for all internal libraries.

Rewriting all mixins as composition functions.

Mounting previous global variables from Vue to app.config.globalProperties .

Declaring global variable types in vue-runtime-core.d.ts for easy usage.

Writing all components with the <script setup lang="ts"> syntax.

Adopting pinia as the new state‑management solution.

Pinia usage

The author explores Pinia's API, noting that the official docs favor the Options API style. They create a store with defineStore , adding state , getters , and actions :

export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0, name: 'Eduardo' }),
  getters: {
    doubleCount: (state) => state.count * 2,
  },
  actions: {
    increment () {
      this.count++
    },
  },
})

Components consume the store via useCounterStore :

import { useCounterStore } from '@/stores/counter'
import { computed } from 'vue'

const store = useCounterStore()
setTimeout(() => {
  store.increment()
}, 1000)
const doubleValue = computed(() => store.doubleCount)

Because the project uses the Composition API exclusively, the author rewrites the store logic into pure composition functions.

Composition functions

A simple useCount.ts file demonstrates a reusable counter:

import { computed, ref } from 'vue'

const useCount = () => {
  const count = ref
(0)
  const doubleCount = computed(() => {
    return count.value * 2
  })
  const setCount = (v: number) => {
    count.value = v
  }
  return { count, doubleCount, setCount }
}
export default useCount

Using the function in a component:

import useCount from './use/useCount'

const { count, setCount } = useCount()
onMounted(async () => {
  console.log('count', count.value) // 0
  setCount(10)
  console.log('count', count.value) // 10
})

The author discovers that calling the composition function multiple times creates separate reactive instances, causing shared state to break. To share state, they move the ref declaration outside the function, making it a module‑level variable:

import { computed, ref } from 'vue'

const count = ref
(0)
const useCount = () => {
  const doubleCount = computed(() => {
    return count.value * 2
  })
  const setCount = (v: number) => {
    count.value = v
  }
  return { count, doubleCount, setCount }
}
export default useCount

Now multiple calls share the same count value, and updates are reflected across all consumers.

To protect the state from external mutation, the author wraps the exported count with readonly :

import { computed, readonly, ref } from 'vue'

const count = ref
(0)
const useCount = () => {
  const doubleCount = computed(() => {
    return count.value * 2
  })
  const setCount = (v: number) => {
    count.value = v
  }
  return {
    // readonly ensures the reference cannot be directly modified
    count: readonly(count),
    doubleCount,
    setCount,
  }
}
export default useCount

Conclusion

By migrating to Vue 3 and leveraging Pinia (or pure composition functions), the author reduces library overhead while retaining powerful state‑management capabilities. They note that abandoning Pinia means re‑implementing helpers like store.$state and store.$patch , but for those comfortable with composition APIs, the codebase becomes leaner and more maintainable.

frontendTypeScriptstate managementComposition APIPiniaVue3
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.