Vue Components Guide

Complete reference for Vue.js components covering Options API, Composition API, and best practices

What are Vue Components?

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).

Basic Component Structure

Single File Component (.vue)

Vue Single File Components (SFCs) use the .vue extension and contain three sections: template, script, and style.

MyComponent.vue Options API
<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>

Component Sections

  • <template> - HTML structure of the component
  • <script> - JavaScript logic and component options
  • <style scoped> - CSS styles (scoped to this component only)

Composition API

Modern approach in Vue 3

The Composition API provides better code organization and reusability using the setup() function or <script setup> syntax.

Counter.vue Composition API
<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>
Reactive Objects Composition API
<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 vs reactive

  • ref() - Use for primitive values (numbers, strings, booleans). Access with .value
  • reactive() - Use for objects and arrays. Access properties directly
  • computed() - Cached values based on reactive dependencies

Props

Passing data from parent to child

Props are custom attributes you can register on a component to pass data from a parent component to a child.

Props with Options API
<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"
/>
Props with Composition API Composition API
<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>

Props Best Practices

  • Props are one-way - never mutate props directly
  • Use camelCase in JavaScript, kebab-case in templates
  • Always specify prop types for better validation
  • Use validators for complex prop requirements

Events (Emits)

Sending data from child to parent

Components can emit custom events to communicate with parent components using $emit or defineEmits.

Custom Events - Options API
<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" />
Custom Events - Composition API Composition API
<script setup>
const emit = defineEmits(['update', 'delete']);

const handleUpdate = (newValue) => {
  emit('update', newValue);
};

const handleDelete = () => {
  emit('delete');
};
</script>

Event Naming

  • Use kebab-case for event names in templates
  • Declare all emitted events in emits option
  • Events flow upward (child → parent)
  • Use v-model for two-way binding shorthand

Slots

Component content distribution

Slots allow you to pass template content from a parent component to a child component. Think of them as placeholders for content.

Default Slot
<!-- 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>
Named Slots
<!-- 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>
Scoped Slots
<!-- 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

Component lifecycle events

Lifecycle hooks allow you to run code at specific stages of a component's lifecycle.

Creation
Mounting
Updating
Unmounting
Options API 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');
  }
};
Composition API Lifecycle Composition API
<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

Reusable composition functions

Composables are reusable functions that encapsulate stateful logic using Composition API. They're similar to React hooks or Vue 2 mixins, but more flexible.

useMouse Composable
// 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>
useFetch Composable
// 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>

Options API vs Composition API

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

Which API Should You Use?

  • Options API - Easier for beginners, good for simple components
  • Composition API - Better for complex logic, code reuse, and TypeScript
  • Both APIs can be used in the same project
  • Vue 3 recommends Composition API for new projects