Logo Catatan Kader Logo Catatan Kader
  • Beranda
Beranda
Frontend Engineering / Vue.js 3

Modul 1: Menguasai Vue 3 &
Composition API Secara Menyeluruh

Vue.js Frontend Development

Modul 01 • Masterclass

"Dari Dasar hingga Arsitektur Skala Enterprise"

Diperbarui: Modul Lengkap
120 Menit Eksekusi & Teori

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 .value untuk 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 pada reactive(). Jika Anda melakukan const { nama } = pengguna;, variabel nama akan kehilangan sifat reaktifnya! Untuk mengatasinya, Vue menyediakan fungsi toRefs(). Namun, lebih aman menggunakan ref() 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.

TodoList.vue
<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>
Kembali ke Beranda Lanjut ke Modul 2: Data via Axios di Vue

Catatan Kader

"Kunci menguasai Vue 3 ada pada pemahaman 'Reactivity'. Pahami kapan harus memakai ref dan kapan harus membungkus logika di dalam computed. Vue melakukan sisanya untukmu."
- Senior Frontend

Selanjutnya

Modul 2: Data via Axios di Vue

Implementasi lifecycle onMounted dan pustaka Axios untuk mengambil data API, lalu menampilkannya dengan v-for.

Catatan Kader Catatan Kader

Catatan ini dikelola untuk keperluan dokumentasi pribadi, pengembangan kemampuan analisis logika, serta standarisasi implementasi sistem teknologi.

© 2026 Catatan Kader. Deployment Active.