Vue 3 Composition API vs Options API
Vue 3 introduced the Composition API as an alternative way to write component logic. While the Options API is still fully supported, the Composition API solves several scaling, typing, and architectural issues in larger web applications.
1. What the Interviewer Is Testing
Interviewers want to see if you can evaluate architectural patterns:
- Logic Sharing: Why are Composables superior to Mixins, Scoped Slots, or Higher-Order Components?
- Code Organization: How do you keep large components maintainable when they grow to hundreds of lines?
- TypeScript Support: How does type inference differ between the two models?
- Compile-time Analysis: How does
<script setup>improve runtime efficiency and code bundle size?
2. Options API: Structuring by Options
In the Options API, you define a component's logic using an object with predefined options such as data, methods, computed, watch, and lifecycle hooks:
<script>
export default {
data() {
return {
searchQuery: '',
results: []
}
},
computed: {
filteredResults() {
return this.results.filter(r => r.includes(this.searchQuery))
}
},
methods: {
async fetchResults() {
this.results = await fetchApi(this.searchQuery)
}
},
mounted() {
this.fetchResults()
}
}
</script>The Problem: Fragmented Logic
As components grow, logic for a single feature (e.g., "search queries") is split across data, computed, and methods. A developer must scroll up and down constantly to understand how a single logical concern behaves.
3. Composition API: Structuring by Logical Concerns
The Composition API allows you to group logical concerns together in a single function or component entry point using reactive APIs (ref, reactive, computed, watch, onMounted):
<script setup>
import { ref, computed, onMounted } from 'vue'
// Logical Concern: Search
const searchQuery = ref('')
const results = ref([])
const filteredResults = computed(() => {
return results.value.filter(r => r.includes(searchQuery.value))
})
async function fetchResults() {
results.value = await fetchApi(searchQuery.value)
}
onMounted(() => {
fetchResults()
})
</script>Encapsulation and Reuse: Composables
Instead of duplicating search logic across pages, you can easily extract it into a clean, testable JavaScript function (a "Composable"):
// useSearch.js
import { ref, computed } from 'vue'
export function useSearch(initialQuery = '') {
const searchQuery = ref(initialQuery)
const results = ref([])
const filteredResults = computed(() => {
return results.value.filter(r => r.includes(searchQuery.value))
})
return { searchQuery, results, filteredResults }
}4. Key Architectural Comparison
Logic Reuse: Composables vs Mixins
In the Options API, logic reuse was primarily solved using Mixins. However, Mixins suffer from three critical flaws:
- Implicit Namespaces: If two mixins use the same variable name (e.g.
isLoading), they silently collide. - Unclear Source: Looking at a template using
this.someData, it is difficult to tell which mixin injected that variable. - No Dynamic Arguments: Mixins cannot easily accept arguments during registration to modify their internal behavior.
Composables solve all three issues: variables are explicitly destructured, renamed to prevent collision, and can take parameters like normal functions.
TypeScript and Typings
- Options API: Because all properties are accessed via the runtime context
this, TypeScript struggles with type inference. Complex typings require heavy workarounds (Vue.extendordefineComponent). - Composition API: Logic is written in pure JavaScript variables and functions, which supports type inference naturally out-of-the-box.
5. Summary Table
| Metric | Options API | Composition API |
|---|---|---|
| Logic Organization | Organized by component option type (data, methods). | Organized by logical features. |
| Logic Reuse | Mixins (problematic collisions, implicit source). | Composables (explicit imports, clean namespace). |
| TypeScript Support | Weak (limited inference on this). | Excellent (uses standard variable types). |
| IDE Intellisense | Average. | Excellent. |
| Code Size | Slightly larger compiled output. | Smaller compiled output (better minification of variable names). |
