Vue Composition API
Organize Vue component logic with refs, reactive objects, and setup functions.
Overview
The Composition API is a Vue 3 approach that groups code by feature instead of separating it into data, methods, computed properties, watchers, and lifecycle options. It makes complex component logic easier to organize, reuse, and type.
Key concepts
- ref creates reactive values
- reactive creates reactive objects
- computed derives values
- Lifecycle hooks and watchers are imported functions
What is the Composition API?
The Composition API is a collection of Vue functions for building component logic. Variables, functions, computed values, watchers, and lifecycle hooks for the same feature can stay together rather than being scattered across option categories.
For example, a dashboard may contain profile, search, notifications, theme, and API logic. Grouping each feature's state and behavior makes a growing component easier to understand and eventually extract into reusable composables.
Options API and Composition API
The Options API organizes code by option type. It remains fully supported and works well, especially for smaller components.
export default {
data() {
return { count: 0 };
},
methods: {
increment() {
this.count++;
}
}
};The Composition API can keep the count state and its behavior next to each other.
<script setup>
import { ref } from "vue";
const count = ref(0);
function increment() {
count.value++;
}
</script>
<template>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
</template>The setup() function
In the traditional Composition API form, setup runs while the component is being created. Values returned from setup become available to the template.
import { ref } from "vue";
export default {
setup() {
const message = ref("Hello Vue!");
function updateMessage() {
message.value = "Welcome to Vue 3";
}
return { message, updateMessage };
}
};Using script setup
The script setup syntax is the concise, recommended single-file component syntax for the Composition API. Top-level variables, imports, and functions are automatically available in the template, so there is no return object.
Reactive values with ref()
ref wraps a value in a reactive object. JavaScript reads and changes it through .value, while Vue automatically unwraps refs in templates.
import { ref } from "vue";
const name = ref("Alice");
const count = ref(0);
name.value = "Sam";
count.value++;Reactive objects with reactive()
reactive creates a deeply reactive proxy for an object. Its properties are accessed normally without .value.
import { reactive } from "vue";
const user = reactive({
name: "Alice",
age: 25
});
user.age++;Avoid destructuring a reactive object directly because the destructured primitive properties lose their reactive connection. Use toRefs when destructuring is necessary, or access properties through the original object.
Derived state with computed()
computed creates a cached reactive value from other reactive state. Use it for derived information instead of storing duplicate values.
import { computed, ref } from "vue";
const price = ref(20);
const quantity = ref(3);
const total = computed(() => price.value * quantity.value);Reacting to changes with watch()
watch runs a side effect when a selected reactive source changes. It is useful for tasks such as saving a preference or fetching search results, not for values that could be computed.
import { ref, watch } from "vue";
const search = ref("");
watch(search, (nextSearch, previousSearch) => {
console.log({ previousSearch, nextSearch });
});Lifecycle hooks
Composition API lifecycle hooks are imported and registered during setup. onMounted runs after the component is added to the page, and onUnmounted is useful for cleaning up timers or event listeners.
<script setup>
import { onMounted, onUnmounted } from "vue";
function handleResize() {
console.log(window.innerWidth);
}
onMounted(() => {
window.addEventListener("resize", handleResize);
});
onUnmounted(() => {
window.removeEventListener("resize", handleResize);
});
</script>Props and events in script setup
Use compiler macros to declare component inputs and emitted events. These macros are available automatically inside script setup.
<script setup>
const props = defineProps({
title: { type: String, required: true }
});
const emit = defineEmits(["save"]);
function save() {
emit("save", props.title);
}
</script>Benefits
- Related state and behavior stay together
- Feature logic can be extracted into composables
- Large components become easier to organize
- TypeScript inference is strong
- Imported functions make dependencies explicit
- Logic can be reused without mixins
Best practices
- Group code by feature or responsibility
- Use ref for individual values and reactive for cohesive objects
- Use computed for derived values
- Use watch only for side effects
- Clean up external listeners and timers
- Keep components focused and extract reusable logic into composables
- Prefer clear names over excessively short variables
Common beginner mistakes
- Forgetting .value in JavaScript when working with refs
- Trying to use .value in the template
- Forgetting to return values from a traditional setup function
- Destructuring reactive objects and losing reactivity
- Using watch for data that should be computed
- Putting unrelated features into one very large setup block
- Forgetting to clean up event listeners or timers
Key takeaways
- The Composition API organizes component code by feature
- script setup removes the need to return template values
- ref and reactive create reactive state
- computed represents derived state
- watch handles reactive side effects
- Lifecycle hooks are imported Vue functions
- Both the Options API and Composition API are valid
- Composable functions enable reusable feature logic