Complete reference for Vue.js components covering Options API, Composition API, and best practices
Components are reusable, self-contained pieces of UI in Vue.js. They encapsulate HTML, CSS, and JavaScript into a single unit that can be used throughout your application. Components can be composed together to build complex user interfaces.
Vue offers two main APIs for writing components: the Options API (traditional approach) and the Composition API (introduced in Vue 3 for better code organization and reusability).
Vue Single File Components (SFCs) use the .vue extension and contain three sections: template, script, and style.
<template> <div class="greeting"> <h1>{{ message }}</h1> <button @click="updateMessage">Update</button> </div> </template> <script> export default { name: 'MyComponent', data() { return { message: 'Hello Vue!' }; }, methods: { updateMessage() { this.message = 'Message updated!'; } } }; </script> <style scoped> .greeting { padding: 20px; text-align: center; } </style>
<template> - HTML structure of the component<script> - JavaScript logic and component options<style scoped> - CSS styles (scoped to this component only)
The Composition API provides better code organization and reusability using the setup() function
or <script setup> syntax.
<template> <div> <p>Count: {{ count }}</p> <button @click="increment">Increment</button> </div> </template> <script setup> import { ref } from 'vue'; // Reactive state const count = ref(0); // Methods const increment = () => { count.value++; }; </script>
<script setup> import { ref, reactive, computed } from 'vue'; // ref - for primitive values const count = ref(0); // reactive - for objects const user = reactive({ name: 'John', age: 30, email: 'john@example.com' }); // Computed properties const doubleCount = computed(() => count.value * 2); const userInfo = computed(() => `${user.name}, ${user.age} years old`); </script>
ref() - Use for primitive values (numbers, strings, booleans). Access with .valuereactive() - Use for objects and arrays. Access properties directlycomputed() - Cached values based on reactive dependenciesProps are custom attributes you can register on a component to pass data from a parent component to a child.
<template> <div> <h2>{{ title }}</h2> <p>{{ description }}</p> </div> </template> <script> export default { props: { title: { type: String, required: true }, description: { type: String, default: 'No description provided' }, count: { type: Number, default: 0, validator(value) { return value >= 0; // Must be non-negative } } } }; </script> <!-- Usage in parent component --> <MyComponent title="Hello" description="Welcome message" :count="42" />
<script setup> import { computed } from 'vue'; const props = defineProps({ title: String, count: { type: Number, default: 0 } }); // Access props const doubledCount = computed(() => props.count * 2); </script>
Components can emit custom events to communicate with parent components using $emit or defineEmits.
<template> <button @click="handleClick">Click Me</button> </template> <script> export default { emits: ['buttonClicked', 'itemSelected'], methods: { handleClick() { // Emit event to parent this.$emit('buttonClicked', { timestamp: new Date(), message: 'Button was clicked' }); } } }; </script> <!-- Parent component --> <MyButton @button-clicked="onButtonClick" />
<script setup> const emit = defineEmits(['update', 'delete']); const handleUpdate = (newValue) => { emit('update', newValue); }; const handleDelete = () => { emit('delete'); }; </script>
emits optionSlots allow you to pass template content from a parent component to a child component. Think of them as placeholders for content.
<!-- Card.vue --> <template> <div class="card"> <slot> <!-- Fallback content if no content provided --> <p>Default card content</p> </slot> </div> </template> <!-- Usage --> <Card> <h2>Custom Title</h2> <p>Custom content goes here</p> </Card>
<!-- Layout.vue --> <template> <div class="layout"> <header> <slot name="header"></slot> </header> <main> <slot></slot> <!-- Default slot --> </main> <footer> <slot name="footer"></slot> </footer> </div> </template> <!-- Usage --> <Layout> <template #header> <h1>My App</h1> </template> <p>Main content here</p> <template #footer> <p>© 2025</p> </template> </Layout>
<!-- UserList.vue - Pass data to slot --> <template> <ul> <li v-for="user in users" :key="user.id"> <slot :user="user" :index="index"> {{ user.name }} </slot> </li> </ul> </template> <!-- Usage - Access slot data --> <UserList :users="users"> <template #default="{ user, index }"> <strong>{{ index + 1 }}.</strong> {{ user.name }} ({{ user.email }}) </template> </UserList>
Lifecycle hooks allow you to run code at specific stages of a component's lifecycle.
export default { // Called before component is created beforeCreate() { console.log('beforeCreate'); }, // Called after instance is created created() { console.log('created - data & methods available'); }, // Called before mounting to DOM beforeMount() { console.log('beforeMount'); }, // Called after mounted to DOM mounted() { console.log('mounted - DOM available, fetch data here'); this.fetchData(); }, // Called when reactive data changes beforeUpdate() { console.log('beforeUpdate'); }, // Called after DOM updated updated() { console.log('updated'); }, // Called before component is destroyed beforeUnmount() { console.log('beforeUnmount - cleanup here'); }, // Called after component is destroyed unmounted() { console.log('unmounted'); } };
<script setup> import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue'; // No need for beforeCreate/created - setup() runs at that stage onBeforeMount(() => { console.log('Before mount'); }); onMounted(() => { console.log('Mounted - fetch data here'); fetchData(); }); onBeforeUpdate(() => { console.log('Before update'); }); onUpdated(() => { console.log('Updated'); }); onBeforeUnmount(() => { console.log('Before unmount - cleanup'); }); onUnmounted(() => { console.log('Unmounted'); }); </script>
| Options API | Composition API | When It Runs |
|---|---|---|
| beforeCreate | - | Before instance initialization |
| created | setup() | After instance created |
| beforeMount | onBeforeMount | Before mounting to DOM |
| mounted | onMounted | After mounted to DOM |
| beforeUpdate | onBeforeUpdate | Before data changes update DOM |
| updated | onUpdated | After DOM updated |
| beforeUnmount | onBeforeUnmount | Before component destroyed |
| unmounted | onUnmounted | After component destroyed |
Composables are reusable functions that encapsulate stateful logic using Composition API. They're similar to React hooks or Vue 2 mixins, but more flexible.
// composables/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 }; } // Usage in component <script setup> import { useMouse } from './composables/useMouse'; const { x, y } = useMouse(); </script> <template> <p>Mouse position: {{ x }}, {{ y }}</p> </template>
// composables/useFetch.js import { ref } from 'vue'; export function useFetch(url) { const data = ref(null); const error = ref(null); const loading = ref(true); async function fetchData() { loading.value = true; try { const response = await fetch(url); data.value = await response.json(); } catch (err) { error.value = err; } finally { loading.value = false; } } fetchData(); return { data, error, loading, refetch: fetchData }; } // Usage <script setup> const { data, error, loading } = useFetch('/api/users'); </script>
| Feature | Options API | Composition API |
|---|---|---|
| State | data() { return {} } |
ref() or reactive() |
| Computed | computed: { } |
computed(() => ) |
| Methods | methods: { } |
Regular functions |
| Watchers | watch: { } |
watch() or watchEffect() |
| Lifecycle | mounted() { } |
onMounted(() => ) |
| Code Organization | By option type | By logical concern |
| Reusability | Mixins | Composables |
| TypeScript | Limited support | Better type inference |