Vue 3 Components: The Complete Guide

Master templates, styling, APIs, and optimization techniques in Vue 3

Vue 3 Last updated:

Introduction to Vue 3 Components

Vue 3 components are the building blocks of Vue applications. They are reusable Vue instances with a name that encapsulate template, logic, and styling. Vue 3 introduced several improvements over Vue 2, including the Composition API, better TypeScript support, and performance optimizations.

// Basic Vue 3 component example
import { defineComponent } from 'vue';

export default defineComponent({
  name: 'MyComponent',
  // Component options go here
});

Components allow you to split your UI into independent, reusable pieces that can be composed together to build complex applications. In Vue 3, components can be defined using either the Options API (similar to Vue 2) or the new Composition API.

Component Templates

Vue components use an HTML-based template syntax that allows you to declaratively bind the rendered DOM to the underlying component instance's data.

Template Syntax

  • Text Interpolation: {{ message }}
  • Directives: Special attributes with the v- prefix
  • Event Handling: v-on:click or @click
  • Two-way Binding: v-model

Template Examples

<template>
  <div>
    <!-- Text interpolation -->
    <p>{{ message }}</p>
    
    <!-- Conditional rendering -->
    <p v-if="showMessage">Visible when showMessage is true</p>
    
    <!-- List rendering -->
    <ul>
      <li v-for="item in items" :key="item.id">
        {{ item.text }}
      </li>
    </ul>
    
    <!-- Event handling -->
    <button @click="handleClick">Click me</button>
    
    <!-- Two-way binding -->
    <input v-model="inputText" type="text">
  </div>
</template>

Vue 3 also supports fragments (multiple root nodes) in templates, which wasn't possible in Vue 2 without a wrapper element.

Styling Vue Components

Vue components can be styled in several ways, each with its own use cases and advantages.

Scoped CSS

Scoped CSS ensures that styles only apply to the current component by adding unique data attributes to elements.

<style scoped>
.button {
  background-color: #3b82f6;
  color: white;
  padding: 0.5rem 1rem;
  border-radius: 0.25rem;
}
</style>

CSS Modules

CSS Modules provide locally scoped CSS with generated class names that you can reference in your templates.

<style module>
.success {
  color: #10b981;
}
.error {
  color: #ef4444;
}
</style>

<template>
  <p :class="$style.success">This will be green</p>
</template>

CSS Pre-processors

Vue supports Sass, Less, and Stylus out of the box when using the Vue CLI.

<style lang="scss" scoped>
$primary-color: #3b82f6;

.button {
  background-color: $primary-color;
  
  &:hover {
    background-color: darken($primary-color, 10%);
  }
}
</style>

Tailwind CSS with Vue

Tailwind CSS works well with Vue components. Here's an example:

<template>
  <button 
    class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
    @click="handleClick"
  >
    Click me
  </button>
</template>

Data Management in Components

Vue components manage their own state through various reactive properties.

Options API Data

export default {
  data() {
    return {
      count: 0,
      message: 'Hello Vue!',
      todos: [
        { id: 1, text: 'Learn Vue' },
        { id: 2, text: 'Build something awesome' }
      ]
    }
  }
}

Composition API Reactive State

import { ref, reactive } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const state = reactive({
      message: 'Hello Vue!',
      todos: [
        { id: 1, text: 'Learn Vue' },
        { id: 2, text: 'Build something awesome' }
      ]
    })
    
    return { count, state }
  }
}

Props - Parent to Child Communication

// Child component
export default {
  props: {
    title: String,
    likes: {
      type: Number,
      default: 0
    },
    isPublished: {
      type: Boolean,
      required: true
    }
  }
}

// Parent component usage
<blog-post title="My Journey with Vue" :likes="42" :is-published="true"></blog-post>

Emits - Child to Parent Communication

// Child component
export default {
  emits: ['update:title'],
  methods: {
    updateTitle() {
      this.$emit('update:title', 'New Title')
    }
  }
}

// Parent component usage
<blog-post :title="post.title" @update:title="post.title = $event"></blog-post>

Lifecycle Hooks

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

Hook Description Options API Composition API
beforeCreate Called immediately after instance initialization Not needed (use setup())
created Called after instance is created Not needed (use setup())
beforeMount Called before mounting begins onBeforeMount
mounted Called after instance is mounted onMounted
beforeUpdate Called when data changes, before DOM is patched onBeforeUpdate
updated Called after DOM is updated onUpdated
beforeUnmount Called before instance is unmounted onBeforeUnmount
unmounted Called after instance is unmounted onUnmounted

Example Usage

// Options API
export default {
  mounted() {
    console.log('Component is mounted!')
    // Good for DOM operations, API calls, etc.
  },
  beforeUnmount() {
    console.log('Component will be destroyed')
    // Good for cleanup (event listeners, intervals, etc.)
  }
}

// Composition API
import { onMounted, onBeforeUnmount } from 'vue'

export default {
  setup() {
    onMounted(() => {
      console.log('Component is mounted!')
    })
    
    onBeforeUnmount(() => {
      console.log('Component will be destroyed')
    })
  }
}

Dynamic Rendering

Vue provides several ways to render content dynamically based on conditions or lists.

Conditional Rendering

<!-- v-if vs v-show -->
<div v-if="type === 'A'">Rendered if type is A (removed from DOM if false)</div>
<div v-else-if="type === 'B'">Rendered if type is B</div>
<div v-else>Rendered if neither A nor B</div>

<div v-show="shouldShow">Always rendered, toggled with display CSS (better for frequent toggles)</div>

List Rendering

<!-- Basic list -->
<ul>
  <li v-for="item in items" :key="item.id">
    {{ item.text }}
  </li>
</ul>

<!-- With index -->
<div v-for="(item, index) in items" :key="item.id">
  {{ index }} - {{ item.text }}
</div>

<!-- Object iteration -->
<div v-for="(value, key) in object" :key="key">
  {{ key }}: {{ value }}
</div>

Dynamic Components

The <component> element can dynamically switch between components using the is attribute.

<!-- CurrentTab changes when tab changes -->
<component :is="currentTab"></component>

<!-- Keep alive preserves state -->
<KeepAlive>
  <component :is="currentTab"></component>
</KeepAlive>

Component Optimization

Optimizing Vue components improves performance and user experience.

Key Optimization Techniques

  • v-once: Render static content once and skip future updates
  • v-memo: New in Vue 3, memoizes a sub-tree to skip unnecessary updates
  • Lazy Loading: Load components only when needed
  • Virtual Scrolling: For large lists
  • Computed Properties: Cache expensive calculations

v-once Example

<!-- Will never update -->
<span v-once>This will never change: {{ msg }}</span>

v-memo Example

<div v-for="item in list" :key="item.id" v-memo="[item.id === selected]">
  <p>ID: {{ item.id }} - Selected: {{ item.id === selected }}</p>
  <p>...more child nodes...</p>
</div>

Lazy Loading Components

// Instead of:
// import HeavyComponent from './HeavyComponent.vue'

// Use:
const HeavyComponent = defineAsyncComponent(() =>
  import('./HeavyComponent.vue')
)

export default {
  components: {
    HeavyComponent
  }
}

Virtual Scrolling

For large lists, use libraries like vue-virtual-scroller:

<RecycleScroller
  class="scroller"
  :items="items"
  :item-size="50"
  key-field="id"
>
  <template v-slot="{ item }">
    <div class="item">
      {{ item.name }}
    </div>
  </template>
</RecycleScroller>

Composition API

The Composition API is a new way to organize component logic introduced in Vue 3.

Basic Example

import { ref, computed, onMounted } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const doubleCount = computed(() => count.value * 2)
    
    function increment() {
      count.value++
    }
    
    onMounted(() => {
      console.log('Component mounted')
    })
    
    return {
      count,
      doubleCount,
      increment
    }
  }
}

Composition API Benefits

  • Better organization of related code
  • Easier code reuse (composables)
  • Better TypeScript support
  • More flexible than Options API organization

Composables (Reusable Logic)

// useCounter.js
import { ref, computed } from 'vue'

export function useCounter() {
  const count = ref(0)
  const doubleCount = computed(() => count.value * 2)
  
  function increment() {
    count.value++
  }
  
  return { count, doubleCount, increment }
}

// Component using the composable
import { useCounter } from './useCounter'

export default {
  setup() {
    const { count, doubleCount, increment } = useCounter()
    
    return { count, doubleCount, increment }
  }
}

<script setup> Syntax (SFC)

The <script setup> syntax is syntactic sugar for using Composition API in Single File Components.

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

const count = ref(0)

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

<template>
  <button @click="increment">Count is: {{ count }}</button>
</template>

Options API

The Options API is the traditional way of writing Vue components, familiar to Vue 2 users.

Complete Example

export default {
  name: 'MyComponent',
  props: {
    title: String,
    initialCount: {
      type: Number,
      default: 0
    }
  },
  data() {
    return {
      count: this.initialCount,
      message: 'Hello Vue!'
    }
  },
  computed: {
    doubleCount() {
      return this.count * 2
    }
  },
  watch: {
    count(newVal, oldVal) {
      console.log(`Count changed from ${oldVal} to ${newVal}`)
    }
  },
  methods: {
    increment() {
      this.count++
    }
  },
  created() {
    console.log('Component created')
  },
  mounted() {
    console.log('Component mounted to DOM')
  }
}

When to Use Options API

  • When migrating from Vue 2
  • For simple components where organization isn't an issue
  • When working with teams familiar with Vue 2
  • For small projects where Composition API benefits aren't needed

Options API vs Composition API

Feature Options API Composition API
Organization By option type (data, methods, etc.) By logical concern
Reusability Mixins (can cause naming collisions) Composables (cleaner reuse)
TypeScript Works but not ideal Excellent support
Learning Curve Easier for beginners Requires understanding reactivity
Flexibility Limited by options structure Very flexible

Conclusion

Vue 3 components offer powerful features for building modern web applications. Whether you choose the Options API or Composition API depends on your project's needs and your team's preferences.

Key Takeaways

  • Vue components combine template, logic, and styling in reusable units
  • The Composition API offers better code organization and reusability for complex components
  • The Options API remains a valid choice, especially for simpler components
  • Vue 3's reactivity system enables efficient dynamic rendering
  • Performance optimizations like v-memo and lazy loading can significantly improve your app

Vue 3's flexibility allows you to choose the right approach for each component in your application. Many projects successfully use both APIs where they make the most sense.

Further Reading