Vue Options API vs Composition API (Implementation Guide)
Purposeβ
This guide is for developers working on Vue projects that use a mix of Options API and Composition API. It outlines key implementation differences, and when/how to use one over the other in real-world situations.
1. Component Structureβ
Options APIβ
export default {
data() {
return {
count: 0,
};
},
computed: {
double() {
return this.count * 2;
},
},
methods: {
increment() {
this.count++;
},
},
};
Composition API (with <script setup>)β
<script setup>
import { ref, computed } from "vue";
const count = ref(0);
const double = computed(() => count.value * 2);
function increment() {
count.value++;
}
</script>
2. Props and Emitsβ
Options APIβ
export default {
props: ["label"],
emits: ["clicked"],
};
Composition APIβ
<script setup>
const props = defineProps(['label']); const emit = defineEmits(['clicked']);
</script>
3. Refs and Template Accessβ
Options APIβ
this.$refs.myInput.focus();
Composition APIβ
const myInput = ref(null);
myInput.value?.focus();
Note: In
<script setup>, template refs are just local variables.Think of
ref="..."as similar todocument.querySelector()β you're creating a reference to a specific DOM or component instance. However, Composition APIref()is also used to create reactive primitives that arenβt tied to DOM β likeconst count = ref(0).
4. Exposing Public Methods to Parentβ
Options APIβ
All methods are exposed automatically via this.$refs.childComponent.
Composition APIβ
Use defineExpose():
const reset = () => {
/* ... */
};
defineExpose({ reset });
Then in parent:
const comp = ref(null);
comp.value?.reset();
Without
defineExpose,ref="childComponent"will return an empty object{}.
5. Lifecycle Hooksβ
Options APIβ
created() {
console.log('Component created');
}
Composition APIβ
import { onMounted } from "vue";
onMounted(() => {
console.log("Component mounted");
});
Note:
created()has no direct equivalent. Just run code at top-level of<script setup>.
6. State Management (e.g., Pinia)β
Both APIs use stores similarly, but Composition API allows cleaner integration:
const store = useMyStore();
store.someAction();
In Options API, you usually assign the store in
data()orcreated().
7. computed vs watchβ
- Use
computedwhen you want a derived value. - Use
watchwhen you want to run side effects on change.
Options APIβ
watch: {
someProp(val) {
this.doSomething(val);
}
}
Composition APIβ
watch(
() => props.someProp,
(val) => {
doSomething(val);
}
);
8. Summary Tableβ
| Feature | Options API | Composition API |
|---|---|---|
| State | data() | ref(), reactive() |
| Computed | computed: | computed() |
| Watchers | watch: | watch() |
| Methods | methods: | regular functions |
| Lifecycle | mounted() | onMounted() etc. |
| Props/Emit | props, emits | defineProps(), defineEmits() |
| Template Refs | this.$refs.x | ref(null) + x.value |
| Public Methods | auto-exposed | defineExpose() |
Recommendationβ
- Use Composition API for new components.
- Stick with Options API in legacy components unless you're refactoring.
- When mixing, keep each component in one style β don't hybridize unless absolutely necessary.
Additional Tipsβ
- Script Setup runs like
created(), so you donβt need a lifecycle method to fetch data or set defaults. - Avoid using
ref(store.something)if you want reactivity β prefercomputed(() => store.something)or use an empty ref then populate it with a watch on the store item. - Prefer
defineExpose()only when you need parent control over a child component. - Use
ref="child"+ref(null)+defineExpose()in child if you want the parent to call methods like.reset()or.focus()on the child.