Modul 1: Menguasai Vue 3 &
Composition API Secara Menyeluruh
Modul 01 • Masterclass
"Dari Dasar hingga Arsitektur Skala Enterprise"
1. Apa itu Vue.js?
Vue (diucapkan /vjuː/, seperti kata bahasa Inggris view) adalah framework JavaScript yang bersifat progresif untuk membangun antarmuka pengguna (User Interfaces). Berbeda dengan framework monolitik yang memaksa Anda menggunakan seluruh ekosistemnya, Vue dirancang dari bawah ke atas agar dapat diadopsi secara bertahap.
Pustaka inti Vue hanya difokuskan pada lapisan tampilan (View Layer), sehingga mudah diambil dan diintegrasikan dengan pustaka lain atau proyek yang sudah ada. Dua fitur inti utama Vue adalah:
- Declarative Rendering: Vue memperluas standar HTML dengan sintaks template yang memungkinkan kita mendeskripsikan secara deklaratif output HTML berdasarkan keadaan (state) JavaScript.
- Reactivity: Vue secara otomatis melacak perubahan state JavaScript dan secara efisien memperbarui Document Object Model (DOM) ketika perubahan terjadi.
Konsep Virtual DOM
Vue menggunakan Virtual DOM. Alih-alih langsung memanipulasi elemen HTML asli di browser (yang sangat lambat), Vue membuat representasi memori ringan dari DOM. Ketika ada variabel yang berubah, Vue menghitung perbedaan (diffing) antara Virtual DOM lama dan baru, lalu hanya menerapkan perubahan spesifik tersebut ke DOM asli. Inilah yang membuat Vue 3 sangat cepat.
2. Apa itu "API" dalam Vue?
Ketika mendengar kata "API" (Application Programming Interface), banyak pemula langsung
memikirkan URL backend (seperti https://api.example.com/users). Dalam
konteks Vue, "API" memiliki makna yang berbeda.
Dalam Vue, API merujuk pada sekumpulan fungsi, metode, dan aturan
sintaks yang disediakan oleh pembuat Vue agar kita (developer) bisa
berkomunikasi dengan sistem internal Vue. Misalnya, fungsi ref() adalah
sebuah API yang disediakan Vue agar kita bisa memberi tahu Vue, "Hei, tolong pantau
variabel ini!".
Mengenal Composition API
Di Vue 2, kita menggunakan Options API. Kita membagi kode berdasarkan
"laci" properti: ada laci untuk data(), laci untuk methods,
dan laci untuk computed. Masalahnya, ketika komponen menjadi sangat besar,
logika untuk satu fitur (misalnya fitur pencarian) akan terpecah-pecah ke berbagai
"laci" tersebut.
Composition API yang diperkenalkan di Vue 3 memecahkan masalah ini. Alih-alih mengelompokkan kode berdasarkan "tipe opsi", kita mengelompokkannya berdasarkan fitur logis. Ini membuat kode jauh lebih mudah dibaca, dipelihara, dan digunakan kembali (reusable).
Perbandingan Kasus: Options API vs Composition API
// 1. Menggunakan Options API (Vue 2 Style)
export default {
data() {
return {
count: 0,
searchQuery: ''
}
},
computed: {
doubleCount() {
return this.count * 2;
}
},
methods: {
increment() {
this.count++;
},
doSearch() {
console.log('Mencari:', this.searchQuery);
}
}
}
// 2. Menggunakan Composition API <script setup> (Vue 3 Style)
<script setup>
import { ref, computed } from 'vue';
// --- Fitur Counter ---
const count = ref(0);
const doubleCount = computed(() => count.value * 2);
const increment = () => {
count.value++;
};
// --- Fitur Pencarian ---
const searchQuery = ref('');
const doSearch = () => {
console.log('Mencari:', searchQuery.value);
};
</script>
Terlihat jelas bahwa pada Composition API, kode terkait Counter dan kode terkait Pencarian bisa dikelompokkan secara berdekatan. Ini adalah "Game Changer" untuk codebase berukuran besar.
3. Inti Vue 3: Reactivity System
Reactivity adalah jantung dari Vue. Ada dua cara utama untuk mendeklarasikan state
reaktif di Vue 3 Composition API: menggunakan ref() dan
reactive().
A. Menggunakan ref()
ref() direkomendasikan sebagai standar default di Vue 3. Ia dapat menerima
tipe data primitif (String, Number, Boolean) maupun tipe data non-primitif (Object,
Array).
Aturan Emas ref:
- Di dalam
<script>, Anda wajib menggunakan.valueuntuk membaca atau mengubah nilainya. - Di dalam
<template>, Vue secara otomatis melakukan unwrapping, sehingga Anda tidak perlu menggunakan.value.
<script setup>
import { ref } from 'vue';
// Deklarasi primitif
const nama = ref('Kader');
const umur = ref(25);
const isLogin = ref(false);
// Deklarasi Array
const hobi = ref(['Coding', 'Membaca']);
// Fungsi untuk mengubah nilai
const rayakanUlangTahun = () => {
// Wajib pakai .value di dalam script!
umur.value++;
console.log(`Selamat ulang tahun ${nama.value}, sekarang umurmu ${umur.value}`);
};
</script>
<template>
<div>
<!-- TIDAK perlu .value di template -->
<h1>Halo, nama saya {{ nama }}</h1>
<p>Umur: {{ umur }} tahun</p>
<button @click="rayakanUlangTahun">Tambah Umur</button>
</div>
</template>
B. Menggunakan reactive()
Berbeda dengan ref, reactive() hanya menerima
tipe data Object (termasuk Array, Map, Set). Ia tidak bisa digunakan untuk nilai
primitif. Keunggulannya adalah Anda tidak perlu menggunakan .value.
<script setup>
import { reactive } from 'vue';
// Hanya bisa menerima Object
const pengguna = reactive({
nama: 'John Doe',
umur: 30,
pekerjaan: 'Frontend Engineer'
});
const ubahPekerjaan = () => {
// Langsung akses properti, tanpa .value
pengguna.pekerjaan = 'Tech Lead';
};
</script>
Peringatan Kader: Hati-hati melakukan destrukturisasi padareactive(). Jika Anda melakukanconst { nama } = pengguna;, variabelnamaakan kehilangan sifat reaktifnya! Untuk mengatasinya, Vue menyediakan fungsitoRefs(). Namun, lebih aman menggunakanref()untuk sebagian besar kasus.
4. Computed Properties & Watchers
Computed Properties
computed() digunakan untuk membuat variabel reaktif yang nilainya
bergantung pada variabel reaktif lainnya. Keunggulan utama computed adalah
Caching. Fungsi di dalam computed hanya akan dijalankan
ulang jika variabel dependensinya berubah.
<script setup>
import { ref, computed } from 'vue';
const keranjang = ref([
{ id: 1, nama: 'Laptop', harga: 15000000 },
{ id: 2, nama: 'Mouse', harga: 500000 }
]);
// totalBelanja akan otomatis ter-update jika isi 'keranjang' berubah
// dan nilainya di-cache hingga perubahan berikutnya terjadi.
const totalBelanja = computed(() => {
return keranjang.value.reduce((total, item) => total + item.harga, 0);
});
</script>
Watchers (watch & watchEffect)
Sementara computed digunakan untuk menghasilkan *nilai baru*,
watch() digunakan untuk menjalankan *side-effect* (efek samping) ketika
sebuah data berubah. Contoh *side-effect* adalah: mengambil data dari API, menyimpan
data ke LocalStorage, atau memanipulasi DOM secara manual.
<script setup>
import { ref, watch } from 'vue';
const kataKunci = ref('');
// Memantau variabel kataKunci
watch(kataKunci, (nilaiBaru, nilaiLama) => {
console.log(`Pencarian diubah dari "${nilaiLama}" menjadi "${nilaiBaru}"`);
// Disini kita bisa panggil API untuk mencari data
// panggilAPIBackend(nilaiBaru);
});
</script>
5. Komponen Lifecycle Hooks
Setiap komponen Vue melewati serangkaian langkah inisialisasi saat dibuat—misalnya, mengatur reaktivitas, menyusun templat, memasang (mounting) komponen ke DOM, dan memperbarui DOM saat data berubah. Di sepanjang proses ini, Vue menjalankan fungsi-fungsi khusus yang disebut Lifecycle Hooks.
Dalam Composition API (<script setup>), Hook yang paling sering
digunakan adalah onMounted dan onUnmounted.
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
const posisiScroll = ref(0);
const updateScroll = () => {
posisiScroll.value = window.scrollY;
};
// Dijalankan HANYA SEKALI ketika komponen telah dirender ke layar
onMounted(() => {
console.log('Komponen siap!');
window.addEventListener('scroll', updateScroll);
});
// Penting: Bersihkan event listener saat komponen dihapus
// untuk mencegah memory leak!
onUnmounted(() => {
window.removeEventListener('scroll', updateScroll);
});
</script>
6. Implementasi Penuh: Aplikasi Task Management Vue 3
Untuk membuktikan bahwa Anda menguasai semua teori di atas, mari kita gabungkan
semuanya. Di bawah ini adalah kode lengkap untuk komponen TodoList.vue yang
mengimplementasikan ref, computed, watch (untuk
LocalStorage), dan Event Handling. Ini adalah standar kode level menengah ke atas.
<script setup>
import { ref, computed, watch, onMounted } from 'vue';
// ==========================================
// 1. STATE MANAGEMENT
// ==========================================
// Array reaktif untuk menyimpan daftar tugas
const tasks = ref([]);
// Variabel untuk input form teks tugas baru
const newTaskTitle = ref('');
// Variabel filter (bisa 'all', 'active', atau 'completed')
const filter = ref('all');
// ==========================================
// 2. COMPUTED PROPERTIES
// ==========================================
// Mengembalikan daftar tugas yang sudah difilter
const filteredTasks = computed(() => {
if (filter.value === 'active') {
return tasks.value.filter(task => !task.completed);
} else if (filter.value === 'completed') {
return tasks.value.filter(task => task.completed);
}
// Default jika filter === 'all'
return tasks.value;
});
// Menghitung sisa tugas yang belum selesai
const remainingTasks = computed(() => {
return tasks.value.filter(task => !task.completed).length;
});
// ==========================================
// 3. METHODS (BUSINESS LOGIC)
// ==========================================
// Fungsi untuk menambah tugas baru
const addTask = () => {
// Validasi: jangan izinkan teks kosong
if (newTaskTitle.value.trim() === '') {
return;
}
// Menambahkan object tugas baru ke awal array
tasks.value.unshift({
id: Date.now(), // Membuat ID unik dengan timestamp
title: newTaskTitle.value,
completed: false
});
// Reset input form
newTaskTitle.value = '';
};
// Fungsi untuk menghapus tugas berdasarkan ID
const removeTask = (taskId) => {
tasks.value = tasks.value.filter(task => task.id !== taskId);
};
// Fungsi untuk membersihkan semua tugas yang sudah selesai
const clearCompleted = () => {
tasks.value = tasks.value.filter(task => !task.completed);
};
// ==========================================
// 4. WATCHERS (SIDE EFFECTS)
// ==========================================
// Menggunakan parameter { deep: true } karena kita
// memantau array of objects. Jika properti 'completed'
// di dalam object berubah, watcher ini akan terpicu.
watch(tasks, (newTasks) => {
// Menyimpan ke Local Storage setiap kali ada perubahan
localStorage.setItem('vue-todo-list', JSON.stringify(newTasks));
}, { deep: true });
// ==========================================
// 5. LIFECYCLE HOOKS
// ==========================================
onMounted(() => {
// Saat komponen dimuat, ambil data dari Local Storage
const savedTasks = localStorage.getItem('vue-todo-list');
if (savedTasks) {
try {
tasks.value = JSON.parse(savedTasks);
} catch (e) {
console.error('Gagal melakukan parsing data LocalStorage', e);
}
}
});
</script>
<template>
<div class="todo-container">
<h1>Tugas Harian Vue 3</h1>
<!-- Form Input Tugas -->
<!-- @submit.prevent otomatis membatalkan event reload page dari form -->
<form @submit.prevent="addTask" class="todo-form">
<input
v-model="newTaskTitle"
type="text"
placeholder="Apa yang perlu diselesaikan hari ini?"
class="todo-input"
>
<button type="submit" class="btn-primary">Tambah</button>
</form>
<!-- Daftar Tugas -->
<ul class="todo-list" v-if="filteredTasks.length > 0">
<li
v-for="task in filteredTasks"
:key="task.id"
:class="{ 'task-completed': task.completed }"
>
<div class="task-content">
<!-- Checkbox untuk status -->
<input type="checkbox" v-model="task.completed">
<span class="task-title">{{ task.title }}</span>
</div>
<button @click="removeTask(task.id)" class="btn-danger">Hapus</button>
</li>
</ul>
<p v-else class="empty-state">Tidak ada tugas yang sesuai dengan filter.</p>
<!-- Footer Panel (Filter & Statistik) -->
<div class="todo-footer" v-if="tasks.length > 0">
<span>{{ remainingTasks }} tugas tersisa</span>
<div class="filter-buttons">
<button :class="{ active: filter === 'all' }" @click="filter = 'all'">Semua</button>
<button :class="{ active: filter === 'active' }" @click="filter = 'active'">Aktif</button>
<button :class="{ active: filter === 'completed' }" @click="filter = 'completed'">Selesai</button>
</div>
<button
v-show="tasks.length > remainingTasks"
@click="clearCompleted"
class="btn-clear"
>
Bersihkan Selesai
</button>
</div>
</div>
</template>
<style scoped>
/* Karena ini menggunakan scope, CSS ini tidak akan bocor ke komponen lain */
.todo-container {
max-width: 600px;
margin: 0 auto;
padding: 2rem;
background: #ffffff;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
/* ... (dan banyak CSS styling lainnya di aplikasi riil) ... */
</style>