This episode covers communication between Vue components: props for incoming data, defineEmits for outgoing events, provide/inject for dependency injection, slots and scoped slots, plus reusable component patterns.

A component rarely stands alone. Real applications are networks of components exchanging data and commands. The ability to design clean communication interfaces — who sends what, who listens to what — is one of the hallmarks of a mature Vue developer.
Episode 6 dissects Vue's four communication channels: props for incoming data, emits for outgoing events, provide/inject for bypassing the component tree, and slots for content composition. Finally, we'll cover reusable component patterns that keep UIs consistent and easy to maintain.
Props are the primary way a component receives data from its parent:
<script setup>
const props = defineProps({
judul: { type: String, required: true },
jumlah: { type: Number, default: 0 },
});
</script>
<template>
<h2>{{ props.judul }}</h2>
<span>{{ props.jumlah }}</span>
</template>defineProps on the child side declares the data contract. The parent just passes them like HTML attributes:
<template>
<ProductBadge judul="Diskon" :jumlah="3" />
</template>judul="Diskon" sends a string literal, while :jumlah="3" sends a reactive value. Props are one-way: a child must not mutate props; it should emit an event if it needs to change data in the parent.
To notify the parent, the child emits a custom event:
<script setup>
const emit = defineEmits(["simpan", "batal"]);
function simpanData() {
emit("simpan", { id: 1 });
}
</script>
<template>
<button @click="simpanData">Simpan</button>
</template>defineEmits(["simpan", "batal"]) declares the events a component can emit. The parent listens to them like ordinary events:
<script setup>
import ProductForm from "./ProductForm.vue";
function onSimpan(data) {
console.log("data diterima:", data);
}
</script>
<template>
<ProductForm @simpan="onSimpan" />
</template>@simpan="onSimpan" runs the handler when the child calls emit("simpan", ...). This forms the data-down, events-up pattern that keeps data flow easy to trace.
When many component layers need the same data, passing it through props one by one becomes tedious and fragile. provide in an ancestor and inject in a descendant cut through the chain:
import { provide, ref } from "vue";
const tema = ref("dark");
provide("tema", tema);import { inject } from "vue";
const tema = inject("tema");inject("tema") fetches the value provided under the "tema" key from the nearest ancestor. Because the key is a string, make sure the naming is consistent so nothing collides.
Provide/inject is useful for global configuration such as theme, locale, or user session. The limitation: the relationship isn't explicitly visible in the component tree, so use it for things that are truly global, not for every kind of communication — communication between nearby components is still better done with props and emits.
Slots let a parent fill content into a child:
<template>
<article class="card">
<header><slot name="judul" /></header>
<div><slot /></div>
</article>
</template>The named slot judul and the default slot hold content from the parent:
<template>
<Card>
<template #judul>Produk Unggulan</template>
<p>Isi konten utama card.</p>
</Card>
</template><template #judul> fills the named slot, and the text inside <Card> fills the default slot. Slots make components flexible without adding props.
A scoped slot sends child data through slot attributes, and the parent receives it with v-slot:
<template>
<ItemList>
<template #default="{ item }">
<b>Item: {{ item }}</b>
</template>
</ItemList>
</template>#default="{ item }" accesses the data the child sent — the child controls the structure, the parent controls the presentation.
For consistency, collect basic UI elements as base components with uniform naming:
src/components/base/BaseButton.vue
src/components/base/BaseInput.vue
src/components/base/BaseModal.vueBase components typically: unify styling, accept props for variation, and spread attributes onto the DOM element.
A rule of thumb: use props for structured data and behavior, slots for free-form content that can't be predicted, and scoped slots when the content needs the component's internal data. Combining all three produces components that are reusable without becoming complicated.
Episode 6 equipped you with Vue's four communication channels: props for incoming data, emits for outgoing events, provide/inject for global values across the component tree, and slots plus scoped slots for flexible content composition.
Key takeaways:
defineEmits declares a child's outgoing events.#nama for flexible content.In the next episode 7, we'll cover directives and custom directives — v-model for two-way binding, v-show, v-cloak, and v-pre, v-model modifiers, plus how to build custom directives with lifecycle hooks for specific needs.