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
computed
when you want a derived value. - Use
watch
when 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.