Lesson 4 of 7
Components & Props
You will learn how components pass data down with props and send messages up with emits. This one-way flow makes it clear where data comes from.
<!-- UserCard.vue — child component -->
<template>
<div class="user-card">
<img :src="avatar" :alt="name" />
<h3>{{ name }}</h3>
<p>{{ role }}</p>
<button @click="$emit('follow', userId)">Follow</button>
</div>
</template>
<script setup>
// defineProps — declare incoming props
const props = defineProps({
userId: { type: Number, required: true },
name: { type: String, required: true },
avatar: { type: String, default: '/default-avatar.png' },
role: { type: String, default: 'user' },
})
// defineEmits — declare events this component can emit
const emit = defineEmits(['follow', 'unfollow'])
// Or with TypeScript:
// const props = defineProps<{ userId: number; name: string; role?: string }>()
// const emit = defineEmits<{ follow: [userId: number] }>()
</script>
<!-- App.vue — parent using UserCard -->
<template>
<div>
<UserCard
v-for="user in users"
:key="user.id"
:userId="user.id"
:name="user.name"
:role="user.role"
@follow="handleFollow"
/>
</div>
</template>
<script setup>
import UserCard from './UserCard.vue'
import { ref } from 'vue'
const users = ref([
{ id: 1, name: "Alice", role: "admin" },
{ id: 2, name: "Bob", role: "user" },
])
function handleFollow(userId) {
console.log(`Following user ${userId}`)
}
</script><!-- Slots — pass template content into a component -->
<!-- Card.vue -->
<template>
<div class="card">
<header v-if="$slots.header" class="card-header">
<slot name="header" />
</header>
<div class="card-body">
<slot /> <!-- default slot -->
</div>
<footer v-if="$slots.footer" class="card-footer">
<slot name="footer" />
</footer>
</div>
</template>
<!-- Using Card with named slots -->
<Card>
<template #header>
<h2>Card Title</h2>
</template>
<p>This goes in the default slot.</p>
<p>Multiple elements are fine.</p>
<template #footer>
<button>Save</button>
<button>Cancel</button>
</template>
</Card>
<!-- Scoped slots — component passes data back to the parent template -->
<!-- DataTable.vue exposes each row's data via scoped slot -->
<DataTable :rows="users">
<template #row="{ row, index }">
<td>{{ index + 1 }}</td>
<td>{{ row.name }}</td>
<td>{{ row.email }}</td>
</template>
</DataTable>Finished reading?Mark it done to track your progress.
Tip: use the ← and → keys to move between lessons.