What are Components?

Components are reusable Vue instances with custom names that encapsulate markup, logic, and styles.

Vue 3 Components: Built using the Composition API or Options API. Composition API is recommended for new projects.

Component APIs

Composition API (Recommended)

Modern, flexible approach

  • Better TypeScript support
  • More flexible code organization
  • Easier logic reuse
  • Smaller bundle size
Options API

Traditional Vue 2 style

  • Familiar structure
  • Good for simple components
  • Still fully supported
  • Easier for beginners

Single-File Components (SFC)

Vue's special file format that encapsulates template, script, and style in one file.

Composition API Example

<script setup>
import { ref, computed } from 'vue'

const count = ref(0)
const doubled = computed(() => count.value * 2)

function increment() {
  count.value++
}
</script>

<template>
  <div class="counter">
    <p>Count: {{ count }}</p>
    <p>Doubled: {{ doubled }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

<style scoped>
.counter {
  padding: 20px;
  border: 1px solid #42b883;
  border-radius: 8px;
}

button {
  background-color: #42b883;
  color: white;
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}
</style>

Options API Example

<script>
export default {
  data() {
    return {
      count: 0
    }
  },
  computed: {
    doubled() {
      return this.count * 2
    }
  },
  methods: {
    increment() {
      this.count++
    }
  }
}
</script>

<template>
  <div class="counter">
    <p>Count: {{ count }}</p>
    <p>Doubled: {{ doubled }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

<style scoped>
.counter {
  padding: 20px;
  border: 1px solid #42b883;
}
</style>

Props

Props are custom attributes for passing data from parent to child components.

Composition API Props

<script setup>
// Define props with TypeScript
const props = defineProps({
  title: {
    type: String,
    required: true
  },
  count: {
    type: Number,
    default: 0
  },
  tags: {
    type: Array,
    default: () => []
  }
})

// Access props
console.log(props.title)
</script>

<template>
  <div>
    <h2>{{ title }}</h2>
    <p>Count: {{ count }}</p>
    <ul>
      <li v-for="tag in tags" :key="tag">{{ tag }}</li>
    </ul>
  </div>
</template>

<!-- Usage -->
<MyComponent
  title="Hello"
  :count="5"
  :tags="['vue', 'javascript']"
/>

TypeScript Props

<script setup lang="ts">
interface Props {
  title: string
  count?: number
  tags?: string[]
}

const props = withDefaults(defineProps<Props>(), {
  count: 0,
  tags: () => []
})
</script>

Custom Events

Emit custom events to communicate from child to parent components.

Emitting Events

<script setup>
// Define emits
const emit = defineEmits(['update', 'delete', 'custom'])

function handleClick() {
  // Emit event with payload
  emit('update', { id: 1, value: 'new value' })
}

function handleDelete(id) {
  emit('delete', id)
}
</script>

<template>
  <button @click="handleClick">Update</button>
  <button @click="handleDelete(123)">Delete</button>
</template>

<!-- Parent component -->
<MyComponent
  @update="handleUpdate"
  @delete="handleDelete"
/>

V-model with Components

<script setup>
// Child component
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])

function updateValue(newValue) {
  emit('update:modelValue', newValue)
}
</script>

<template>
  <input
    :value="modelValue"
    @input="updateValue($event.target.value)"
  />
</template>

<!-- Parent usage -->
<script setup>
import { ref } from 'vue'
const text = ref('')
</script>

<template>
  <CustomInput v-model="text" />
  <p>{{ text }}</p>
</template>

Slots

Slots allow you to pass template content to child components.

Default Slot

<!-- Card.vue -->
<template>
  <div class="card">
    <div class="card-header">
      <slot name="header">Default Header</slot>
    </div>
    <div class="card-body">
      <slot>Default content</slot>
    </div>
    <div class="card-footer">
      <slot name="footer"></slot>
    </div>
  </div>
</template>

<!-- Usage -->
<Card>
  <template #header>
    <h3>My Card Title</h3>
  </template>

  <p>This is the card content</p>

  <template #footer>
    <button>Save</button>
  </template>
</Card>

Scoped Slots

<!-- List.vue -->
<script setup>
const props = defineProps(['items'])
</script>

<template>
  <ul>
    <li v-for="item in items" :key="item.id">
      <slot :item="item" :index="index">
        {{ item.name }}
      </slot>
    </li>
  </ul>
</template>

<!-- Usage -->
<List :items="users">
  <template #default="{ item, index }">
    <strong>{{ index + 1 }}.</strong> {{ item.name }}
  </template>
</List>

Lifecycle Hooks

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

Composition API Hooks

<script setup>
import {
  onBeforeMount,
  onMounted,
  onBeforeUpdate,
  onUpdated,
  onBeforeUnmount,
  onUnmounted
} from 'vue'

onBeforeMount(() => {
  console.log('Before mount')
})

onMounted(() => {
  console.log('Component mounted')
  // Fetch data, setup listeners
})

onBeforeUpdate(() => {
  console.log('Before update')
})

onUpdated(() => {
  console.log('Component updated')
})

onBeforeUnmount(() => {
  console.log('Before unmount')
  // Cleanup listeners
})

onUnmounted(() => {
  console.log('Component unmounted')
})
</script>

Lifecycle Diagram

Hook When it runs Common uses
onBeforeMount Before component is mounted Final changes before rendering
onMounted After component is mounted API calls, DOM manipulation
onBeforeUpdate Before reactive data changes Access old DOM state
onUpdated After DOM re-renders Access updated DOM
onBeforeUnmount Before component unmounts Cleanup, save data
onUnmounted After component unmounts Final cleanup