Dari Todo List Sederhana ke Aplikasi yang Scalable
Yaho, sebelumnya di Episode 4 kita udah sukses membuat Todo List app yang berfungsi lengkap dengan semua fitur CRUD. Kalau belum baca, langsung aja cek Tutorial Vue JS: Membuat Todo List App CRUD Lengkap - Eps 4 dulu ya.
Tapi sekarang bayangkan, code kamu udah jadi super panjang dalam satu file. Template ribuan baris, method-method bertumpuk-tumpuk, semua tercampur jadi satu. Pas buka file itu, sulit dipahami mana bagian form, mana bagian list, mana bagian counter. Ini adalah masalah yang sering dihadapi developer ketika proyek mulai besar.
Solusinya adalah memecah aplikasi itu menjadi beberapa komponen yang lebih kecil. Di Episode 5 ini, kita bakal pelajari cara melakukan ini. Kita akan kunalin props untuk komunikasi data dari komponen induk ke komponen anak, dan events untuk komunikasi aksi dari komponen anak ke komponen induk. Dua konsep ini adalah skill fundamental yang harus kamu kuasai untuk membangun aplikasi Vue yang scalable dan mudah dimaintain.
Apa itu Komponen? Fondasi Arsitektur Berbasis Komponen

Komponen dalam Vue.js adalah bagian antarmuka yang independen dan bisa dipakai ulang. Setiap komponen punya logika, template, dan styling-nya sendiri, jadi bisa berdiri sendiri dan dipake ulang berkali-kali di aplikasi kamu.
Bayangkan komponen seperti bagian-bagian dari mobil. Mesin, roda, dan pintu masing-masing adalah komponen independen. Mesin buat menghasilkan tenaga, roda buat menggerakkan mobil, pintu buat dibuka dan ditutup. Gabungin semuanya, jadilah mobil utuh yang berfungsi sempurna. Yang mantap, kalau ada masalah dengan mesin, kamu tinggal ganti mesin aja tanpa perlu rebuild seluruh mobil. Begitu juga komponen di Vue. Bug di satu komponen? Tinggal perbaiki komponen itu, komponen lain nggak kena dampak.
Manfaat dari Arsitektur Berbasis Komponen
Menggunakan komponen memberikan beberapa keuntungan yang bakal kamu rasakan langsung:
Pertama, kemampuan pakai ulang. Komponen yang kamu buat bisa dipake di banyak tempat tanpa nulis ulang. Misalnya komponen Button yang kamu buat bisa dipake di TodoForm, TodoItem, TodoStats. Cukup buat sekali, pakai dimana aja. Jauh lebih efisien.
Kedua, kemudahan pemeliharaan. Code jadi lebih terorganisir karena setiap komponen punya tanggung jawab yang spesifik. Pas ada yang perlu diubah, kamu langsung tahu edit komponen mana. Nggak perlu cari-cari di ribuan baris kode kayak dulu.
Ketiga, kolaborasi tim jadi lebih lancar. Developer A bisa ngerjain TodoForm sementara Developer B ngerjain TodoItem, mereka nggak akan saling mengganggu. Masing-masing fokus di komponen mereka sendiri.
Terakhir, pengujian jadi lebih mudah. Komponen yang kecil dan terfokus jauh lebih gampang diuji. Kalau ada masalah, kamu langsung tahu ada di mana.
Cara Komponen Berkomunikasi
Komponen nggak berdiri completely isolated. Mereka perlu berkomunikasi dengan komponen lain. Komunikasi ini terjadi melalui dua cara utama: props dan events. Props adalah cara komponen induk kirim data ke komponen anak. Events adalah cara komponen anak kirim aksi balik ke komponen induk.
Dengan paham konsep ini, kamu udah siap untuk mulai praktik. Yuk lanjut ke bagian selanjutnya buat belajar props dan events lebih detail.
Props: Cara Parent Kirim Data ke Child

Sekarang kita belajar gimana komponen berkomunikasi. Yang pertama adalah props, ini adalah cara parent component kirim data ke child component. Props adalah data yang hanya bisa dibaca (read-only) di child, nggak boleh diubah. Ini one-way data flow, data cuma mengalir dari parent ke child.
Apa itu Props?
Props adalah bagian dari properties, singkatnya data dari atas ke bawah. Child component bisa pake data itu tapi nggak boleh ubah. Kenapa? Soalnya kalau semua child bisa ubah data sesuka hati, bakalan chaos. Parent adalah source of truth, dia yang punya kontrol. Kalau child perlu ngubah data, dia harus emit event ke parent, bukan langsung mutate props.
Cara Define Props di Child
Untuk terima props, child harus define dulu props yang diinginkan. Di Vue 3 pake defineProps():
// TodoItem.vue (Child)
const props = defineProps({
title: String,
count: Number,
isActive: Boolean
})
Setelah itu, props bisa langsung dipakai di template dengan nama yang sama: {{ title }}, {{ count }}, dll.
Cara Pass Props dari Parent
Parent kirim props via attribute binding dengan : prefix:
<!-- TodoApp.vue (Parent) -->
<TodoItem
:title="todo.text"
:completed="todo.completed"
:id="todo.id"
/>
Tuh kan, : berarti nilai-nya adalah JavaScript expression. So todo.text dari parent masuk ke props title di child. Di template pake kebab-case (:is-active) tapi di JavaScript tetap camelCase (isActive).
Props dengan Type & Validation
Untuk aplikasi yang lebih solid, selalu define tipe data dan tambah validation:
const props = defineProps({
// Required prop
title: {
type: String,
required: true
},
// Dengan default value
count: {
type: Number,
default: 0
},
// Custom validator
status: {
type: String,
validator: (value) => {
return ['active', 'completed'].includes(value)
}
}
})
Vue bakal validate otomatis. Kalau ada yang nggak sesuai, bakal warning di console. Ini sangat helpful buat debug.
Best Practices Props
Ada tiga rules penting yang jangan dilupain:
Pertama, selalu define tipe props. Jangan pernah pakai props tanpa tipe. Ini akan selamatkan kamu dari banyak bug yang susah dicari.
Kedua, perhatiin naming. Di JavaScript pake camelCase (isActive), di template pake kebab-case (:is-active). Standard seperti ini.
Ketiga, jangan mutate props! Jangan ubah nilai props langsung. Kalau perlu ubah, buat variable lokal atau emit event. Mutating props adalah bug yang sulit dicari dan bakal bikin aplikasi unpredictable.
Props adalah fundamental skill yang bakal kamu pake terus-terusan. Dengan ini, parent-child communication jadi clean dan maintainable.
Events: Cara Child Kirim Action ke Parent

Kalau props adalah parent yang ngomong ke child, events adalah child yang ngomong balik ke parent. Events adalah mekanisme komunikasi dari child ke parent. Child nggak bisa langsung ubah data parent (karena props read-only), tapi bisa notify parent kalau ada sesuatu yang perlu diperhatiin. Ini adalah respons dari child berupa aksi atau informasi yang dikirim ke parent.
Apa itu Events?
Events adalah cara child component berkomunnikasi dengan parent. Child bisa emit (kirim) event, dan parent akan handle (terima dan proses) event tersebut. Ketika emit event, child juga bisa pass data via payload. Misalnya saat user delete todo, child component emit delete event dan pass id todo-nya ke parent. Parent terima event itu dan process delete logic-nya.
Cara Define dan Emit Events
Di child component, gunakan defineEmits() buat define event apa yang bakal di-emit:
// TodoItem.vue (Child)
const emit = defineEmits(['update', 'delete'])
function handleUpdate() {
emit('update', updatedData)
}
function handleDelete() {
emit('delete', todoId)
}
Lihat? defineEmits() adalah array dari event names yang bakal kita kirim. Setelah itu, kita panggil emit() dengan nama event dan data yang mau dikirim. Data itu adalah payload.
Handle Events di Parent
Parent component terima events lewat @event-name binding:
<!-- TodoApp.vue (Parent) -->
<TodoItem
@update="handleUpdate"
@delete="handleDelete"
/>
Tanda @ adalah shorthand dari v-on:, jadi @update artinya listen ke event update. Ketika child emit update, function handleUpdate di parent akan dijalanin. Parent bisa akses data yang dikirim child dari parameter function.
Contohnya:
function handleUpdate(updatedData) {
// updatedData adalah payload dari emit('update', updatedData)
console.log(updatedData)
}
function handleDelete(todoId) {
// todoId adalah payload dari emit('delete', todoId)
todos.value = todos.value.filter(t => t.id !== todoId)
}
Event Naming Convention
Ada best practice untuk naming events yang harus diikutin. Gunakan kebab-case untuk event names, bukan camelCase. Contohnya @todo-updated bukan @todoUpdated. Ini konsisten dengan HTML attribute convention.
Kedua, buat event names yang descriptive. Jangan cuma pake nama generik seperti @delete. Lebih baik @item-deleted, @todo-completed, atau nama yang lebih spesifik. Ini membuat code lebih readable dan jelas apa yang terjadi.
Contohnya di aplikasi Todo List:
@todo-added- ketika todo baru ditambahkan@todo-deleted- ketika todo dihapus@todo-toggled- ketika status todo diubah@filter-changed- ketika filter diubah
Dengan event naming yang good, orang lain yang baca code kamu langsung paham apa yang sedang terjadi tanpa perlu banyak komentar.
Dengan kombinasi props dan events, parent-child communication jadi lancar dan predictable. Props kirim data ke bawah, events kirim aksi ke atas. Ini adalah core dari Vue component architecture yang powerful dan elegant.
Refactor Todo List: Dari Satu File Jadi Multiple Components
Sekarang kita praktik teori yang udah dipelajari. Kita ambil aplikasi Todo List dari Episode 4 yang masih dalam satu file, terus pecah jadi beberapa komponen lebih kecil. Ini adalah contoh real dari props dan events.
Struktur Komponen Todo List
Ini adalah struktur komponen yang bakal kita buat:
TodoApp (parent)
├── TodoForm
├── TodoStats
└── TodoList
└── TodoItem (multiple)
TodoApp adalah parent yang hold semua data. TodoForm untuk input todo baru. TodoStats untuk display counter. TodoList untuk wrapper list, dan TodoItem untuk satu item todo.
TodoForm Component: Input Form
TodoForm nggak perlu props apapun, cuma emit event add-todo ke parent:
// components/TodoForm.vue
<script setup>
import { ref, nextTick } from 'vue'
const emit = defineEmits(['add-todo'])
const newTodo = ref('')
const inputField = ref(null)
async function handleSubmit() {
if (newTodo.value.trim() === '') {
alert('Todo tidak boleh kosong!')
return
}
emit('add-todo', newTodo.value)
newTodo.value = ''
// Focus ke input field setelah todo ditambah
await nextTick()
inputField.value?.focus()
}
</script>
<template>
<div class="todo-input">
<input
ref="inputField"
v-model="newTodo"
@keyup.enter="handleSubmit"
type="text"
placeholder="Ketik todo baru..."
class="input-field"
/>
<button @click="handleSubmit" class="btn-add">Add</button>
</div>
</template>
<style scoped>
/* FORM INPUT & BUTTON */
.todo-input {
display: flex;
gap: 10px;
margin-bottom: 30px;
background: white;
padding: 20px;
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
}
.input-field {
flex: 1;
padding: 12px 16px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 15px;
transition: all 0.3s ease;
outline: none;
}
.input-field:focus {
border-color: #42b983;
box-shadow: 0 0 0 3px rgba(66, 185, 131, 0.1);
}
.btn-add {
padding: 12px 28px;
background: #42b983;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: 600;
font-size: 15px;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(66, 185, 131, 0.4);
}
.btn-add:hover {
background: #35a372;
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(66, 185, 131, 0.6);
}
.btn-add:active {
transform: translateY(0);
}
/* RESPONSIVE DESIGN */
@media (max-width: 600px) {
.todo-input {
flex-direction: column;
padding: 15px;
}
.input-field {
width: 100%;
}
.btn-add {
width: 100%;
}
}
</style>
Komponen ini standalone. Manage input sendiri, validate sendiri, terus emit event ke parent. Parent yang handle logic menambah todo. Clean dan simple.
TodoItem Component: Satu Item Todo
TodoItem terima props todo dan emit toggle sama delete events:
// components/TodoItem.vue
<script setup>
const props = defineProps({
todo: {
type: Object,
required: true
}
})
const emit = defineEmits(['toggle', 'delete'])
</script>
<template>
<li class="todo-item">
<input
type="checkbox"
:checked="todo.completed"
@change="emit('toggle', todo.id)"
class="checkbox"
/>
<span
:class="{ completed: todo.completed }"
class="todo-text"
>
{{ todo.text }}
</span>
<button
@click="emit('delete', todo.id)"
class="btn-delete"
>
Delete
</button>
</li>
</template>
<style scoped>
/* TODO LIST ITEMS */
.todo-item {
padding: 16px 20px;
border-bottom: 1px solid #f0f0f0;
display: flex;
align-items: center;
gap: 12px;
transition: all 0.3s ease;
background: white;
list-style: none;
}
.todo-item:hover {
background-color: #f8f9fa;
padding-left: 24px;
}
.todo-item:last-child {
border-bottom: none;
}
/* CHECKBOX CUSTOM STYLING */
.checkbox {
width: 20px;
height: 20px;
cursor: pointer;
accent-color: #667eea;
flex-shrink: 0;
}
/* TODO TEXT */
.todo-text {
flex: 1;
word-break: break-word;
color: #333;
transition: all 0.3s ease;
}
.completed {
text-decoration: line-through;
color: #999;
opacity: 0.7;
}
/* DELETE BUTTON */
.btn-delete {
background: #f5576c;
color: white;
border: none;
padding: 8px 14px;
border-radius: 6px;
cursor: pointer;
font-weight: 600;
font-size: 12px;
transition: all 0.3s ease;
flex-shrink: 0;
box-shadow: 0 2px 8px rgba(245, 87, 108, 0.3);
}
.btn-delete:hover {
background: #e63e5a;
transform: scale(1.1);
box-shadow: 0 4px 12px rgba(245, 87, 108, 0.5);
}
/* RESPONSIVE DESIGN */
@media (max-width: 600px) {
.todo-item {
padding: 12px 15px;
}
.todo-item:hover {
padding-left: 15px;
}
.btn-delete {
padding: 6px 12px;
font-size: 11px;
}
}
</style>
TodoItem terima todo dari parent dan tampilin. User klik checkbox atau delete, emit event dengan todo id. Parent yang process logic.
TodoList Component: Wrapper untuk Items
TodoList loop todos dan forward events dari TodoItem ke parent. Yang penting, jangan lupa import TodoItem:
// components/TodoList.vue
<script setup>
import TodoItem from './TodoItem.vue'
defineProps({
todos: Array
})
const emit = defineEmits(['toggle', 'delete'])
</script>
<template>
<div class="todo-list">
<ul>
<TodoItem
v-for="todo in todos"
:key="todo.id"
:todo="todo"
@toggle="emit('toggle', $event)"
@delete="emit('delete', $event)"
/>
</ul>
</div>
</template>
<style scoped>
/* TODO LIST */
.todo-list {
background: white;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
}
.todo-list ul {
list-style: none;
padding: 0;
margin: 0;
}
</style>
TodoList loop semua todos dan render TodoItem untuk setiap todo. Import TodoItem sangat penting, kalau nggak di-import, Vue nggak tahu komponen apa yang dimaksud. TodoList cuma orchestrator yang pass props dan forward events.
TodoStats Component: Display Counter
TodoStats simple, cuma terima props dan tampilin:
// components/TodoStats.vue
<script setup>
defineProps({
total: Number,
active: Number,
completed: Number
})
</script>
<template>
<div class="todo-stats">
<div class="stat">
<strong>Total</strong>
{{ total }}
</div>
<div class="stat">
<strong>Active</strong>
{{ active }}
</div>
<div class="stat">
<strong>Completed</strong>
{{ completed }}
</div>
</div>
</template>
<style scoped>
/* COUNTERS DISPLAY */
.todo-stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
margin: 25px 0;
}
.stat {
background: white;
padding: 20px;
border-radius: 12px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
text-align: center;
transition: all 0.3s ease;
}
.stat:hover {
transform: translateY(-5px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.12);
}
.stat strong {
display: block;
color: #42b983;
font-size: 12px;
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 8px;
}
.stat {
font-size: 28px;
font-weight: bold;
color: #333;
}
/* RESPONSIVE DESIGN */
@media (max-width: 600px) {
.todo-stats {
grid-template-columns: 1fr;
}
}
</style>
Komponen ini murni presentational, nggak ada logic. Cuma display data dari props.
TodoApp: Parent Component dengan Logic
Parent hold semua data dan logic:
// views/TodoApp.vue
<script setup>
import { ref, computed } from 'vue'
import TodoForm from '@/components/TodoForm.vue'
import TodoList from '@/components/TodoList.vue'
import TodoStats from '@/components/TodoStats.vue'
const todos = ref([])
const filter = ref('all')
const inputField = ref(null)
const totalTodos = computed(() => todos.value.length)
const activeTodos = computed(() =>
todos.value.filter(t => !t.completed).length
)
const completedTodos = computed(() =>
totalTodos.value - activeTodos.value
)
const filteredTodos = computed(() => {
if (filter.value === 'active') {
return todos.value.filter(t => !t.completed)
}
if (filter.value === 'completed') {
return todos.value.filter(t => t.completed)
}
return todos.value
})
function addTodo(text) {
todos.value.push({
id: Date.now(),
text,
completed: false
})
}
function toggleTodo(id) {
const todo = todos.value.find(t => t.id === id)
if (todo) todo.completed = !todo.completed
}
function deleteTodo(id) {
todos.value = todos.value.filter(t => t.id !== id)
}
function setFilter(newFilter) {
filter.value = newFilter
}
</script>
<template>
<div class="todo-container">
<h1>Todo List</h1>
<TodoForm
@add-todo="addTodo"
:input-field="inputField"
/>
<TodoStats
:total="totalTodos"
:active="activeTodos"
:completed="completedTodos"
/>
<div class="filter-buttons">
<button
:class="{ active: filter === 'all' }"
@click="setFilter('all')"
class="btn-filter"
>
All ({{ totalTodos }})
</button>
<button
:class="{ active: filter === 'active' }"
@click="setFilter('active')"
class="btn-filter"
>
Active ({{ activeTodos }})
</button>
<button
:class="{ active: filter === 'completed' }"
@click="setFilter('completed')"
class="btn-filter"
>
Completed ({{ completedTodos }})
</button>
</div>
<div v-if="filteredTodos.length === 0" class="empty-state">
Belum ada todo untuk kategori ini. Mulai tambahkan todo baru sekarang!
</div>
<TodoList
v-else
:todos="filteredTodos"
@toggle="toggleTodo"
@delete="deleteTodo"
/>
</div>
</template>
<style scoped>
/* GLOBAL STYLES */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* CONTAINER & LAYOUT */
.todo-container {
max-width: 700px;
margin: 0 auto;
padding: 30px 20px;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #f5f7fa;
min-height: 100vh;
}
.todo-container h1 {
text-align: center;
color: #333;
margin-bottom: 30px;
font-size: 28px;
}
/* FILTER BUTTONS */
.filter-buttons {
display: flex;
justify-content: center;
gap: 10px;
margin: 25px 0;
flex-wrap: wrap;
}
.btn-filter {
padding: 10px 20px;
border: 2px solid #e0e0e0;
background-color: white;
color: #333;
cursor: pointer;
border-radius: 8px;
font-weight: 600;
font-size: 14px;
transition: all 0.3s ease;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
.btn-filter:hover {
border-color: #42b983;
color: #42b983;
transform: scale(1.05);
box-shadow: 0 4px 12px rgba(66, 185, 131, 0.2);
}
.btn-filter.active {
background-color: #42b983;
color: white;
border-color: #42b983;
box-shadow: 0 4px 15px rgba(66, 185, 131, 0.4);
}
/* EMPTY STATE */
.empty-state {
padding: 60px 20px;
text-align: center;
color: #999;
font-size: 16px;
background: white;
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
}
/* RESPONSIVE DESIGN */
@media (max-width: 600px) {
.todo-container {
padding: 20px 15px;
min-height: auto;
}
.todo-container h1 {
font-size: 24px;
margin-bottom: 20px;
}
.filter-buttons {
justify-content: center;
}
.btn-filter {
font-size: 12px;
padding: 8px 16px;
}
}
</style>

Keuntungan dari Refactoring
Dengan pecah jadi beberapa komponen, kita dapat beberapa keuntungan. Pertama, reusability. TodoItem bisa dipakai di komponen lain. TodoForm bisa dimodifikasi untuk input form yang berbeda.
Kedua, maintainability. Bug di TodoForm langsung tahu cari di file TodoForm.vue. Nggak perlu scroll ribuan baris.
Ketiga, team collaboration. Developer A bisa ngerjain TodoForm, Developer B ngerjain TodoItem, mereka nggak saling mengganggu.
Terakhir, testing lebih mudah. Component kecil jauh lebih gampang ditest. Bisa test TodoItem separately dari parent-nya.
Ini adalah power dari component-based architecture. Sekarang kamu udah lihat implementasi real dari props dan events dalam aplikasi yang beneran jalan.
Props vs Events: Ringkasan Komunikasi Komponen
Props dan events adalah dua mekanisme yang bekerja bersama untuk membuat komponen berkomunikasi. Penting banget paham perbedaannya dan kapan pake masing-masing.
Props: Data Mengalir ke Bawah
Props adalah data dari parent ke child. Parent boss yang punya data, child pekerja yang display data. Data ini read-only, child nggak boleh ubah nilainya. Props dikirim via attribute binding dengan : prefix.
Contohnya di parent:
<TodoItem :todo="todoData" :completed="true" />
Di child, props diterima dan dipake untuk display, tapi nggak bisa diubah. Kalau child perlu ubah, harus emit event ke parent.
Events: Aksi Mengalir ke Atas
Events kebalikan dari props. Ketika child perlu ngomong ke parent, dia emit event. Parent listen event dan handle aksinya. Events dikirim dari child dengan emit(), dan di parent di-handle dengan @event-name.
Contohnya di child:
emit('delete', todoId)
Di parent:
<TodoItem @delete="handleDelete" />
Parent yang terima event dan ubah data. Child nggak perlu tahu bagaimana parent proses event.
Data Flow: Siklus Lengkap
Komunikasi parent-child adalah siklus yang berfungsi seperti ini:
Parent (punya data & logic)
↓ props
Child (display data)
↑ events
Parent (update data)
↓ props (updated)
Child (display updated data)
Parent punya data. Parent pass ke child via props. Child display data dan tunggu user interaction. Saat ada aksi (delete, toggle, dll), child emit event. Parent terima event, proses logic, dan update data. Data baru pass balik ke child via props.
Siklus ini berulang setiap kali ada user interaction. Data flow jadi transparent dan mudah di-track.
Kapan Pake Props, Kapan Pake Events
Rule-nya simple. Parent kirim data ke child: pake props. Child notify parent ada sesuatu: pake events. Jangan ubah props langsung di child, selalu emit event terus biarkan parent handle.
Dengan paham perbedaan props dan events, kamu punya mental model yang solid buat bangun komponen architecture yang clean. Props dan events adalah foundation dari semua komunikasi komponen di Vue.
Best Practices: Aturan Emas Component Development
Ada beberapa best practices yang harus kamu pegang saat develop komponen. Ini bukan sekedar saran, tapi aturan yang terbukti ngebantu buat code lebih maintainable. Mari kita lihat satu per satu.
Best Practices Props
Selalu define tipe dan validation. Jangan pass props tanpa tipe:
const props = defineProps({
title: {
type: String,
required: true
},
count: {
type: Number,
default: 0
}
})
Gunakan default values. Jangan pernah ubah props langsung, selalu emit event ke parent. Keep props sederhana, jangan pass object yang terlalu kompleks.
Best Practices Events
Gunakan nama descriptive dengan kebab-case: @item-deleted bukan @delete. Payload structure yang jelas, konsisten dalam format data yang dikirim. Jangan emit terlalu banyak events dari satu komponen. Kalau terlalu banyak, komponen perlu dipecah lebih kecil.
Document event contracts, jelaskan apa event itu dan payload-nya. Ini ngebantu orang lain pakai komponen kamu dengan benar.
Best Practices Components
Single responsibility, setiap komponen satu tanggung jawab. TodoForm handle input, TodoItem display item, TodoStats display counter. Keep komponen kecil dan terfokus, kalau 500+ baris udah signal harus dipecah. Reusable when possible. Naming harus PascalCase dan descriptive: TodoItem bukan Item.
File Organization
Struktur folder yang recommended:
src/
├── components/ ← Reusable komponen
│ ├── TodoForm.vue
│ ├── TodoItem.vue
│ ├── TodoList.vue
│ └── TodoStats.vue
├── views/ ← Page-level komponen
│ └── TodoApp.vue
└── App.vue
components/ buat komponen yang bisa dipakai ulang. views/ buat page-level atau smart komponen yang manage state. Dengan struktur ini, project mudah di-navigate dan dimaintain.
Maintain consistency di seluruh project. Kalau team setuju naming pattern atau validation style, semua harus follow. Consistency ini penting buat kolaborasi tim yang lancar.
Kesimpulan: Mastery Component Architecture
Selamat! Kamu udah selesaikan Episode 5 tentang Vue components, props, dan events. Dari satu file besar jadi multiple components yang clean adalah bukti progress kamu.
Recap: Apa yang Udah Kita Pelajari
Components adalah reusable building blocks fundamental dalam Vue. Props adalah data dari parent ke child, read-only. Events adalah aksi dari child ke parent, via emit dan @event-name.
Todo List berhasil dipecah jadi TodoForm, TodoItem, TodoList, dan TodoStats. Setiap komponen punya tanggung jawab yang jelas. Data flow transparent dan predictable.
Benefits yang Dicapai
Better code organization. Code lebih terstruktur dan mudah dipahami. Easier maintenance. Bug fix atau update langsung tahu komponen mana yang edit. Component reusability. TodoItem dan TodoForm bisa dipake di project lain. Development jadi lebih cepat karena banyak code bisa di-reuse.
Selanjutnya
Episode 6 bakal bahas Vue Router buat membuat aplikasi dengan multiple pages. Kamu akan belajar navigate antara pages dan manage aplikasi lebih kompleks.
Tapi yang paling penting, praktik terus. Buat project-project kecil dengan component architecture yang benar. Refactor project lama dengan memecah jadi multiple components. Semakin banyak praktik, semakin dalam pemahaman kamu.
Belajar di BuildWithAngga
Mau perdalam Vue dan component patterns lebih lanjut? BuildWithAngga punya banyak kursus comprehensive. Bukan hanya Vue, tapi juga Laravel, Next.js, React, UI/UX design, dan tech stack lainnya. Plus ada banyak kelas gratis tanpa ribet.
Komunitas BWA juga supportive. Banyak developer siap bantu kalau kamu stuck. Jangan berhenti belajar. Terus practice, terus explore. Setiap developer bagus dimulai dari fundamentals yang solid dan consistent practice.
Kunjungi BuildWithAngga.com untuk explore lebih banyak kursus dan resource. Terima kasih sudah belajar bersama Episode 5. Sampai ketemu di Episode 6 tentang Vue Router!