Vue Options API → Composition API
Moving from the Options API to the Composition API improves logic reuse and TypeScript inference. Both APIs coexist in Vue 3, so do it incrementally — component by component, with <script setup> and composables replacing mixins.
Last verified · Updated May 22, 2026
Converting Options API components to the Composition API with <script setup> is a low-risk, incremental modernization available once you are on Vue 3. Map data to ref/reactive, computed/watch to their function forms, and lifecycle methods to onMounted-style hooks. Both APIs coexist, so migrate one component at a time behind passing tests.
The incremental model
The Composition API does not replace the Options API — Vue 3 supports both, even within the same project. That means there is no cutover: you can convert a single component to <script setup>, extract shared logic into composables, and leave the rest on the Options API. Adopt it where logic reuse or TypeScript inference is painful, and leave simple components alone.
Transformation rules
| Options API | Composition API |
|---|---|
| data() { return { count: 0 } } | const count = ref(0) |
| nested/object state | reactive({ ... }) |
| computed: { double() {} } | const double = computed(() => ...) |
| watch: { value() {} } | watch(value, (next, prev) => ...) |
| mounted() | onMounted(() => ...) |
| beforeUnmount() | onBeforeUnmount(() => ...) |
| mixins | composables (use* functions) |
Component conversion
<!-- Options API -->
<script>
export default {
data() {
return { count: 0 }
},
computed: {
double() {
return this.count * 2
},
},
mounted() {
console.log('mounted')
},
}
</script>
<!-- Composition API with <script setup> -->
<script setup>
import { ref, computed, onMounted } from 'vue'
const count = ref(0)
const double = computed(() => count.value * 2)
onMounted(() => console.log('mounted'))
</script>When the pattern applies
- Components with logic you want to share across files (extract a composable instead of a mixin).
- Components where TypeScript inference under the Options API is awkward.
- Components you are already touching for the Vue 3 upgrade.
Composables vs mixins
- Mixins merge implicitly and clash on naming; composables are explicit imports with clear return values.
- A composable is a plain `use*()` function that calls ref/reactive/computed/watch and returns reactive state.
- Prefer composables for new shared logic; convert mixins to composables as you touch the components that use them.
Convert this Vue Options API component to the Composition API using <script setup>. Map data to ref/reactive, computed/watch to their function forms, and lifecycle hooks (mounted to onMounted, beforeUnmount to onBeforeUnmount). Replace `this.` access with the local refs. If the component uses a mixin, extract the shared logic into a use* composable. Do not change the template or the component's public props/emits, and keep tests passing.
Safety: Convert one component at a time and keep the template and public API unchanged. Do not bundle unrelated refactors.
Edge cases
- Reactivity loss: destructuring a reactive() object breaks reactivity — use toRefs.
- `this` no longer exists in <script setup>; replace it with the local refs and helpers.
- Two-way mixin state rarely maps cleanly — model it explicitly in the composable's return value.
- Template refs and provide/inject replace patterns that previously reached through component internals.
Conversion checklist
- data/computed/watch mapped to ref/reactive/computed/watch
- Lifecycle methods mapped to on* hooks
- Mixins extracted into composables where shared logic exists
- Template and public props/emits unchanged
- Component tests still pass after conversion
Related paths
Official sources
Backs the breaking-change and migration-step claims.
Backs the breaking-change and migration-step claims.
Copy-ready AI prompts
Structured prompts for an AI coding assistant. Inspect first, then execute incrementally, and keep a human in the review loop.
You are helping with a Vue migration: Options API to Composition API modernization. Do not edit files yet. First inspect the repository and report: 1. The exact vue version in package.json and the lockfile, plus vue-router, vuex, @vue/cli-service, and any vue plugins, and whether each has a Vue 3-compatible release. 2. The application entry point: `new Vue(...)` vs `createApp(...)`, and global registrations (Vue.use, Vue.component, Vue.directive, Vue.prototype / Vue.config.globalProperties). 3. Usage of removed Vue 2 features: filters, the global event bus / $on / $off / $once, `$children`, and `.sync` modifiers. 4. Components relying on multiple-root-node assumptions, v-model usage on custom components, and any `functional` components. 5. The build tool (Vue CLI vs Vite), test runner, and the build/lint/test commands. Return: a migration risk summary, the highest-risk files, a suggested migration order, the commands to run before editing, and any questions that need human confirmation.
Safety: Inspection only. The agent must not modify files in this step.
Works with Claude Code, Cursor, GitHub Copilot
Refactor shared logic out of this Vue mixin into a Composition API composable. Create a `use*` function that calls ref/reactive/computed/watch and returns the reactive state and methods the components need. Update each component that used the mixin to call the composable from <script setup> instead. Preserve behavior and keep tests passing; do not change templates or public component APIs.
Safety: One mixin at a time. Preserve behavior exactly and keep templates and public APIs unchanged.
Works with Claude Code, Cursor, GitHub Copilot
Test plan
Commands
npm run lintnpm test -- --watch=falsenpm run build
Manual checks
- Render: confirm each converted component renders identically to its Options API version.
- Reactivity: verify computed values and watchers still fire after conversion.
- Composables: confirm extracted composables return reactive state (no lost reactivity from destructuring).
Regression risks
- Lost reactivity from destructuring a reactive() object without toRefs.
- Mixin-to-composable extraction subtly changing initialization order.
- `this`-based access left behind after moving to <script setup>.
Acceptance criteria
- Each converted component renders and behaves identically to before.
- Shared logic lives in composables, not mixins.
- Lint, tests, and build pass with no template or public-API changes.