How to Transform Your Vue2 Codebase to Vue3 with Script Setup

This article walks through the practical steps for migrating a Vue2 project to Vue3, covering script‑setup syntax, composition‑API patterns, removal of mixins and global variables, file‑naming conventions, and reusable composable functions to achieve a cleaner, more maintainable codebase.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
How to Transform Your Vue2 Codebase to Vue3 with Script Setup

Overview

Vue 3 has been stable for a long time, but many projects still use Vue 2 patterns that limit the benefits of the new composition‑based architecture. The following guidelines show how to migrate to a modern Vue 3 codebase using <script setup>, composable functions, and semantic file naming.

Using &lt;script setup&gt;

The <script setup> block replaces the traditional export default { … } component definition. All component logic lives directly inside the block, eliminating the need for an explicit export statement and the this context.

<script setup lang="ts">
import { ref, onMounted } from 'vue'

const title = ref<string>('')

onMounted(() => {
  title.value = 'Demo'
})
</script>

<template>
  <div :class="$style.container">{{ title }}</div>
</template>

<style lang="scss" module>
/* component styles */
</style>

Key differences compared with the Options API:

No export default – the file itself is the component.

Composition functions ( ref, onMounted, etc.) are used directly; methods and this are unnecessary.

Router and other plugins are accessed via dedicated composables (e.g., useRouter) instead of this.$router, which improves type inference.

import { useRouter } from 'vue-router'
const router = useRouter()

Component imports become straightforward:

<script setup lang="ts">
import CompA from './CompA.vue'
</script>

<template>
  <div :class="$style.container">
    <CompA />
  </div>
</template>

Same‑Name Shorthand (Vue 3.4+)

When a variable name matches a prop or attribute name, the binding can be shortened. This reduces boilerplate but requires disabling the ESLint rule vue/valid-v-bind if linting is enabled.

<script setup lang="ts">
const id = ref('container')
const title = ref('标题')
</script>

<template>
  <div :id>
    <CompA :title />
  </div>
</template>
{
  "rules": {
    "vue/valid-v-bind": "off"
  }
}

Replacing Mixins with Composables

Mixins rely on this and are hard to type‑check. A composable encapsulates reusable logic and can be imported where needed.

import { onMounted, ref } from 'vue'

const useNavbar = () => {
  const navbarProps = ref<any>({})
  const setNavbar = (newProps?: any) => {
    navbarProps.value = { ...navbarProps.value, ...newProps, ...commonProps }
    if (navbarProps.value.title && typeof document !== 'undefined') {
      document.title = navbarProps.value.title
    }
  }
  onMounted(() => {
    // init logic
  })
  return { navbarProps, setNavbar }
}

export default useNavbar

Usage:

import useNavbar from './useNavbar'
const { navbarProps, setNavbar } = useNavbar()

Avoiding Global Properties

Vue 2 often attached utilities to Vue.prototype. In Vue 3 the preferred pattern is a composable that returns the needed functions, optionally exposing them through app.config.globalProperties for legacy code.

// vueThis.ts – safe wrapper around getCurrentInstance
import { getCurrentInstance } from 'vue'
export default () => {
  return getCurrentInstance()?.appContext.config.globalProperties
}
// useRequest.ts – isolated HTTP helper
export default () => {
  const get = async (uri: string, params: any = {}) => {
    return await Request.get(uri, params)
  }
  const post = async (uri: string, params: any = {}) => {
    return await Request.post(uri, params)
  }
  return { get, post }
}

Typical usage:

import useRequest from './useRequest'
const { post } = useRequest()
post('/url', {})

For TypeScript type augmentation, extend @vue/runtime-core:

import request from '@host/request'

declare module '@vue/runtime-core' {
  interface ComponentCustomProperties {
    $request: typeof request
  }
}

One Composable per Concern

Adopt the principle “one use, one responsibility”. Examples: useRequest – handles all HTTP interactions. useNavbar – manages navigation‑bar state and document title. useDevice – encapsulates device‑specific logic. useLoad – controls loading indicators.

Semantic File Naming (Avoid index.vue )

In <script setup> the component name is derived from the file name, which also becomes the route name. Using generic index.vue files leads to ambiguous route names and makes keep-alive inclusion difficult. Prefer descriptive file names.

- pages
  - home
    - img
    - components
      - CompA.vue
      - CompB.vue
    - Home.vue   // instead of index.vue
  - rule
    - Rule.vue   // instead of index.vue

When using keep-alive, the component name can be referenced directly:

<router-view v-slot="{ Component }">
  <keep-alive :include="['Home']">
    <component :is="Component" />
  </keep-alive>
</router-view>

Conclusion

Migrating to Vue 3 is most effective when you:

Adopt <script setup> for concise component definitions.

Replace mixins and prototype globals with dedicated composable functions.

Leverage same‑name shorthand (Vue 3.4+) where possible.

Structure the project with semantic file names instead of generic index.vue files.

Follow the “one composable per concern” principle to keep code modular and type‑safe.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

frontendmigrationbest practicesComposition APIVue3Script Setup
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

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.