Composition: The Composition API
Before the advent of Vue 3, developers relied primarily on the Options API to structure their components. As applications grew in scale, the limitations of this approach regarding logic organization and code reuse became increasingly apparent. The Composition API was created specifically to address these pain points. Built around the core philosophy of "composition," it allows developers to group code by logical concerns rather than arbitrary option types, making it effortless to split and reuse logic.
The Composition API is a set of function-based APIs introduced in Vue 3. It primarily encompasses:
- Reactive APIs:
ref,reactive,computed,watch, and more. - Lifecycle Hooks:
onMounted,onUnmounted,onUpdated, and more. - Dependency Injection:
provideandinject. - The Entry Point:
setup()and its syntactic sugar<script setup>.
Unlike the Options API, which splits code across options like data, methods, and computed, the Composition API enables us to keep pieces of code related to the exact same feature right next to each other, drastically improving code locality and cohesion.
In complex components, the Options API typically suffers from several inherent drawbacks:
- Fragmented Logic: For instance, a single search feature might have its data defined in
data, its handling methods inmethods, and its side effects inwatch. Developers are forced to constantly jump back and forth across different sections of a file to understand a single feature. - Poor Reusability: While mixins allow logic sharing, they carry hidden risks such as namespace collisions and unclear property sources.
- Weak Type Inference: Code structures relying heavily on
thiscontext often lack robust support for TypeScript.
By leveraging plain functions to encapsulate logic, the Composition API naturally circumvents these challenges.
Core API Usage Examples
ref and reactive
ref is ideal for wrapping primitive types, accessed via .value, whereas reactive is well-suited for object types.
<script setup>
import { ref, reactive } from 'vue'
const count = ref(0)
const user = reactive({ name: 'John Doe', age: 25 })
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">Clicked {{ count }} times</button>
<p>{{ user.name }}, {{ user.age }} years old</p>
</template>
computed and watch
import { ref, computed, watch } from 'vue'
const firstName = ref('Doe')
const lastName = ref('John')
// Computed property: automatically tracks dependencies
const fullName = computed(() => lastName.value + ' ' + firstName.value)
// Watcher: reacts to data changes
watch(firstName, (newVal, oldVal) => {
console.log(`Name changed from ${oldVal} to ${newVal}`)
})
Modern Lifecycle Hooks
Lifecycle hooks in the Composition API are prefixed with on and registered directly inside the setup scope:
import { onMounted, onUnmounted } from 'vue'
onMounted(() => {
console.log('Component has been mounted')
})
onUnmounted(() => {
console.log('Component has been unmounted')
})
Composables: The Best Practice for Logic Reuse
The greatest value of the Composition API lies in composition itself—extracting independent logic into reusable functions known as composables (conventionally named with a use prefix). For example, encapsulating mouse tracking logic:
// useMouse.js
import { ref, onMounted, onUnmounted } from 'vue'
export function useMouse() {
const x = ref(0)
const y = ref(0)
function update(event) {
x.value = event.pageX
y.value = event.pageY
}
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
return { x, y }
}
This can then be utilized seamlessly inside any component:
<script setup>
import { useMouse } from './useMouse'
const { x, y } = useMouse()
</script>
<template>
<p>Mouse position: {{ x }}, {{ y }}</p>
</template>
Compared to mixins, composables offer clear origins, prevent naming collisions, and provide native TypeScript type inference. Furthermore, multiple composables can call and nest within each other, building flexible layers of logic.
Best Practices
- Prioritize the
<script setup>syntactic sugar for cleaner code; it is also the officially recommended approach. - Use
reffor primitive values andreactivefor objects; when in doubt, sticking exclusively torefreduces mental overhead. - Once component logic exceeds a certain complexity threshold, break it down into standalone composables to keep component files concise and readable.
- Always clean up side effects like timers and event listeners inside
onUnmountedto prevent memory leaks.
Conclusion
The essence of the Composition API is "composing logic through functions." It gathers related code, separates unrelated concerns, and offers a safer, clearer mechanism for reuse compared to mixins. For new projects, adopting the Composition API entirely is highly recommended. For legacy codebases, Vue 3 maintains full backward compatibility with the Options API, enabling a smooth and gradual migration. Mastering this suite of APIs will significantly elevate the maintainability and development efficiency of large-scale frontend applications.