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