Vue Dasar
Target: pemula yang sudah memahami JavaScript dasar, lalu ingin Versi: Vue 3.x (Composition API) Prasyarat: JavaScript Dasar · JavaScript DOM Fokus modul pembelajaran ini: **template → state → reactive → computed → Semua contoh utama menggunakan
<script setup>agar cocok dengan gaya Vue
Cara Belajar
🟢 Fundamental
→ wajib untuk mulai membuat UI dengan Vue
🟡 Lanjutan
→ pelajari setelah fundamental nyaman
🔴 Advanced / Reference
→ penting ketika kebutuhan aplikasi meningkatMental model:
STATE / DATA
|
v
TEMPLATE
|
v
DOMDaftar Isi
🟢 Fundamental
- Pengenalan
- Membuat Project
- Hello Vue
- API Style
- Template
- Template Attributes
- JS Expression di Template
- Directive
- State
- DOM Update
- Reactive
- Computed Properties
- Style
- Conditional Rendering
- List Rendering
- Event Handling
- Input Binding
🟡 Lanjutan
- Watchers
- Template Refs
- Lifecycle Hooks
- Component
- Component Props
- Component Event
- Component Model
- Fallthrough Attributes
- Component Slot
- Dynamic Component
- Provide dan Inject
🔴 Advanced / Reference
- Component Instance
- Peta Ingatan Cepat
- Tabel Ringkasan
- Cheat Code Vue 10 Detik
- Urutan Belajar yang Disarankan
- Mini Project
- Referensi Resmi
1. 🟢 Pengenalan
Vue.js adalah framework JavaScript untuk membuat UI.
Cara berpikir sederhananya:
STATE / DATA
│
▼
TEMPLATE
│
▼
DOMKetika state berubah:
state berubah
↓
Vue mendeteksi perubahan
↓
DOM diperbaruiContoh:
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<button @click="count++">
Count: {{ count }}
</button>
</template>Output awal:
Count: 0Klik tombol:
Count: 1
Count: 2
Count: 3
...Kunci: Vue membuat UI mengikuti state.
2. 🟢 Membuat Project
Cara umum membuat project Vue modern menggunakan Vite:
npm create vue@latestIkuti pertanyaan setup, kemudian:
cd nama-project
npm install
npm run devStruktur sederhananya:
project/
├── src/
│ ├── components/
│ ├── App.vue
│ └── main.js
├── public/
├── index.html
├── package.json
└── vite.config.jsFile penting:
main.js
│
▼
App.vue
│
├── Component A
├── Component B
└── Component CKunci:
main.js → titik masuk
App.vue → root component
*.vue → component3. 🟢 Hello Vue
Component Vue biasanya memiliki:
<script setup>
const message = 'Hello Vue!'
</script>
<template>
<h1>{{ message }}</h1>
</template>Output:
Hello Vue!Vue Single File Component biasanya terdiri dari:
┌─────────────────────────┐
│ <script setup> │
│ JavaScript │
├─────────────────────────┤
│ <template> │
│ HTML-like template │
├─────────────────────────┤
│ <style scoped> │
│ CSS │
└─────────────────────────┘4. 🟢 API Style
Vue menyediakan dua gaya utama:
Options API
<script>
export default {
data() {
return {
count: 0
}
},
methods: {
increment() {
this.count++
}
}
}
</script>Composition API
Gaya modern yang sering digunakan bersama <script setup>:
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>Perbandingan
Options API
data
methods
computed
watch
↓
dikelompokkan berdasarkan jenis fitur
Composition API
ref
computed
watch
function
↓
dikelompokkan berdasarkan logika/fiturKunci: untuk belajar Vue modern, prioritaskan Composition API + <script setup>.
5. 🟢 Template
Template adalah bagian HTML-like tempat kita mendeskripsikan struktur UI yang akan ditampilkan ke layar.
<script setup>
const name = 'Budi'
</script>
<template>
<h1>Halo {{ name }}</h1>
<p>Selamat belajar Vue.</p>
</template>Output:
Halo Budi
Selamat belajar Vue.Text Interpolation
Syntax {{ }} (Mustache syntax) disebut text interpolation. Ini adalah cara paling dasar untuk menyisipkan data atau variabel JavaScript ke dalam teks HTML secara dinamis.
<p>Halo, {{ name }}!</p>Vue akan merender nilai variabel name sebagai teks polos (plain text). Jika nilai name berubah, tampilan teks di browser otomatis diperbarui.
Kunci: {{ }} digunakan untuk menyisipkan teks dinamis ke dalam elemen HTML.
6. 🟢 Template Attributes
Attribute binding adalah mekanisme untuk menghubungkan atribut elemen HTML (seperti src, href, title, disabled, class, dan style) dengan nilai data atau ekspresi JavaScript secara dinamis menggunakan directive v-bind atau shorthand titik dua (:).
<script setup>
import { ref } from 'vue'
const imageUrl = '/logo.png'
const title = 'Logo Vue'
const isDisabled = ref(true)
</script>
<template>
<!-- Shorthand :src sama artinya dengan v-bind:src -->
<img :src="imageUrl" :alt="title">
<!-- Binding boolean attribute -->
<button :disabled="isDisabled">
Tombol Nonaktif
</button>
</template>Output di DOM:
<img src="/logo.png" alt="Logo Vue">
<button disabled>Tombol Nonaktif</button>Atribut Boolean
Untuk atribut boolean seperti disabled, checked, atau readonly:
- Jika nilainya truthy (
true,'yes'), atribut akan disertakan pada elemen HTML. - Jika nilainya falsy (
false,null,undefined), atribut otomatis dihilangkan oleh Vue.
Kunci:
:src="..." → binding atribut src
:disabled="..." → binding atribut boolean
:class="..." → dynamic class
:style="..." → dynamic style: adalah shorthand resmi untuk v-bind:.
7. 🟢 JS Expression di Template
Template dapat menjalankan expression JavaScript sederhana.
<script setup>
const name = 'Budi'
const age = 20
</script>
<template>
<p>{{ name.toUpperCase() }}</p>
<p>{{ age + 1 }}</p>
<p>{{ age >= 18 ? 'Dewasa' : 'Anak' }}</p>
</template>Output:
BUDI
21
DewasaGunakan expression sederhana.
Untuk logika kompleks, lebih baik gunakan:
computed
method/functiondaripada membuat template terlalu rumit.
8. 🟢 Directive
Directive adalah atribut HTML khusus bawaan Vue yang selalu diawali dengan awalan v-. Directive memberikan instruksi khusus kepada Vue untuk memanipulasi elemen DOM ketika nilai ekspresinya berubah.
Anatomi Directive
v-on:click.prevent="submit"
──┬─ ──┬── ───┬─── ───┬──
│ │ │ └─ Value: ekspresi / handler JavaScript yang dijalankan
│ │ └────────── Modifier: postfix khusus untuk mengubah perilaku event/binding
│ └───────────────── Argument: target nama event atau nama attribute HTML
└────────────────────── Directive Name: nama directive bawaan Vue (v-on, v-bind, dll.)Daftar Directive Umum
v-bind (:) → menghubungkan attribute HTML ke JavaScript
v-on (@) → mendengarkan event DOM (klik, submit, keyup)
v-model → two-way data binding pada form input
v-if / v-else → conditional rendering (tambah/hapus elemen di DOM)
v-show → conditional display (toggle display: none)
v-for → list rendering (perulangan data array)
v-html → merender raw HTML (hati-hati risiko XSS)
v-text → mengupdate textContent elemenShorthand Paling Sering Digunakan
v-bind:src="url" → :src="url"
v-on:click="handler" → @click="handler"Contoh:
<!-- Menggunakan shorthand resmi -->
<button @click="count++">
Tambah
</button>Kunci:
: → shorthand v-bind (attribute)
@ → shorthand v-on (event)
# → shorthand v-slot (slot)9. 🟢 State
State adalah data yang menentukan kondisi dan tampilan UI. Ketika data state berubah, Vue akan otomatis memperbarui tampilan UI yang menggunakan data tersebut.
Gunakan ref() untuk membuat reactive state (baik tipe primitif seperti string/number/boolean maupun object).
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<p>Total: {{ count }}</p>
<button @click="increment">Tambah</button>
</template>Output awal:
Total: 0Setelah klik tombol:
Total: 1Mengapa .value?
ref() membungkus nilai data ke dalam sebuah objek reaktif dengan properti .value.
Aturan akses ref():
- Di JavaScript (
<script>): wajib menggunakan.valueuntuk membaca atau mengubah data:console.log(count.value) // membaca nilai count.value++ // mengubah nilai - Di template (
<template>): tidak perlu.valuekarena Vue otomatis melakukan unwrapping (auto-unwrap):<p>{{ count }}</p>
Kunci:
ref()
↓
.value saat di <script> (JavaScript)
tanpa .value saat di <template> (HTML)10. 🟢 DOM Update
Vue memperbarui DOM secara otomatis ketika reactive state berubah.
<script setup>
import { ref } from 'vue'
const message = ref('Halo')
function changeMessage() {
message.value = 'Halo Vue!'
}
</script>
<template>
<p>{{ message }}</p>
<button @click="changeMessage">
Ubah
</button>
</template>Sebelum klik:
HaloSetelah klik:
Halo Vue!Diagram:
message.value berubah
│
▼
Vue Reactive System
│
▼
DOM Update (Asynchronous Batch)Update Asynchronous & nextTick()
Ketika data state berubah, Vue tidak langsung mengupdate DOM saat itu juga secara sinkron, melainkan mengumpulkannya dalam antrean (buffer) dan melakukan batch update secara asynchronous agar performa rendering tetap efisien.
Jika Anda perlu mengakses elemen DOM tepat setelah DOM selesai diperbarui (misalnya mengambil ukuran tinggi elemen baru atau memfokuskan input), gunakan fungsi nextTick():
<script setup>
import { ref, nextTick } from 'vue'
const message = ref('Halo')
async function changeMessage() {
message.value = 'Halo Vue!'
// Tunggu hingga Vue selesai mengupdate DOM
await nextTick()
console.log('DOM sudah selesai diperbarui!')
}
</script>Kunci: Vue melakukan DOM update secara asynchronous; gunakan await nextTick() jika butuh menunggu DOM selesai diperbarui.
11. 🟢 Reactive
reactive() adalah fungsi alternatif untuk membuat objek atau array menjadi reactive secara mendalam (deep reactive).
Berbeda dengan ref(), reactive() tidak membungkus data dalam .value, melainkan langsung mengembalikan JavaScript Proxy dari objek tersebut.
<script setup>
import { reactive } from 'vue'
const user = reactive({
name: 'Budi',
age: 20
})
function birthday() {
user.age++
}
</script>
<template>
<p>{{ user.name }}</p>
<p>{{ user.age }}</p>
<button @click="birthday">
Ulang Tahun
</button>
</template>Output awal:
Budi
20Klik:
Budi
21Keterbatasan reactive()
Meskipun terlihat lebih praktis tanpa .value, reactive() memiliki beberapa keterbatasan penting karena menggunakan JavaScript Proxy:
- Hanya untuk tipe objek: Tidak bisa digunakan untuk data primitif (
string,number,boolean).// ❌ Error / tidak bekerja const count = reactive(0) - Tidak boleh di-reassign: Mengganti seluruh objek akan memutus koneksi reaktivitas Proxy aslinya.
let user = reactive({ name: 'Budi' }) // ❌ Reaktivitas putus! user = reactive({ name: 'Andi' }) - Kehilangan reaktivitas saat destructuring:
const user = reactive({ name: 'Budi', age: 20 }) // ❌ `name` menjadi string biasa, bukan reactive lagi const { name } = user
ref vs reactive
ref(value)
↓
Bisa untuk tipe data apa saja (primitif & objek)
Akses lewat .value di script
Best practice standar Vue 3
reactive(object)
↓
Hanya untuk objek/array
Akses langsung tanpa .value
Bekerja dengan JavaScript ProxyKunci: Gunakan ref() sebagai pilihan utama (default) untuk semua kebutuhan state di Vue 3 modern.
12. 🟢 Computed Properties
computed() digunakan untuk membuat nilai turunan dari reactive state.
<script setup>
import { ref, computed } from 'vue'
const price = ref(10000)
const quantity = ref(3)
const total = computed(() => {
return price.value * quantity.value
})
</script>
<template>
<p>Total: {{ total }}</p>
</template>Output:
Total: 30000Diagram:
price (10000) ────┐
├──> computed(() => ...) ──> total (30000)
quantity (3) ────┘Keunggulan computed: Caching Otomatis
Mengapa menggunakan computed() daripada fungsi/method biasa di template?
- Caching Berdasarkan Dependensi:
computed()mengingat (cache) hasil perhitungannya. Selama nilaipricedanquantitytidak berubah, pemanggilantotalberkali-kali akan langsung mengambil hasil cache tanpa menghitung ulang. - Method Tanpa Cache: Jika kita memanggil fungsi biasa
getTotal()di template, fungsi tersebut akan dieksekusi ulang setiap kali terjadi re-render pada komponen, meskipun datanya tidak berubah.
Sifat Default: Read-Only
Secara default, nilai computed bersifat read-only (hanya bisa dibaca, tidak boleh di-assign langsung):
// ❌ Peringatan / Error di console
total.value = 50000Kunci:
state → computed → nilai turunan (dengan caching)Gunakan computed untuk menghitung nilai/tampilan, bukan untuk menjalankan side effect (untuk side effect gunakan watch).
13. 🟢 Style
Vue menyediakan integrasi khusus untuk manipulasi atribut class dan style menggunakan data JavaScript dinamis (:class dan :style).
1. Class Binding: Object Syntax
Class akan diterapkan jika nilai propertinya bernilai truthy (true):
<script setup>
import { ref } from 'vue'
const isActive = ref(true)
const hasError = ref(false)
</script>
<template>
<div :class="{ active: isActive, 'text-danger': hasError }">
Menu Item
</div>
</template>
<style>
.active {
color: green;
font-weight: bold;
}
.text-danger {
color: red;
}
</style>Output di DOM:
<div class="active">Menu Item</div>2. Class Binding: Array Syntax
Gunakan array jika ingin menerapkan beberapa class CSS secara bersamaan:
<script setup>
import { ref } from 'vue'
const activeClass = ref('active')
const sizeClass = ref('text-large')
</script>
<template>
<div :class="[activeClass, sizeClass]">
Konten Utama
</div>
</template>Output di DOM:
<div class="active text-large">Konten Utama</div>3. Inline Style Binding
Gunakan objek JavaScript untuk mengatur properti CSS inline:
<script setup>
import { ref } from 'vue'
const textColor = ref('blue')
const fontSize = ref(18)
</script>
<template>
<div :style="{ color: textColor, fontSize: fontSize + 'px' }">
Teks Berwarna
</div>
</template>Output di DOM:
<div style="color: blue; font-size: 18px;">Teks Berwarna</div>Kunci:
:class="{ active: isActive }" → toggle class berdasarkan kondisi boolean
:class="[classA, classB]" → gabungkan beberapa class
:style="{ color: textColor }" → styling CSS inline dinamis14. 🟢 Conditional Rendering
Conditional rendering adalah teknik untuk menampilkan, menyembunyikan, atau mengganti elemen UI di layar berdasarkan kondisi logika (truthy/falsy) dari state.
Vue menyediakan beberapa directive untuk conditional rendering:
v-if → render elemen jika kondisi true
v-else-if → kondisi alternatif
v-else → fallback jika semua kondisi di atas false
v-show → toggle tampilan elemen (CSS display)Contoh:
<script setup>
import { ref } from 'vue'
const isLoggedIn = ref(false)
</script>
<template>
<p v-if="isLoggedIn">Dashboard</p>
<p v-else>Silakan login</p>
</template>Output:
Silakan loginv-if vs v-show
v-if
↓
Benar-benar menambah atau menghapus elemen dari DOM tree.
Lebih hemat biaya render awal jika kondisi awal false.
v-show
↓
Elemen selalu ada di DOM, hanya mengubah CSS display: none.
Lebih hemat biaya jika elemen sangat sering di-toggle (tampil/sembunyi).Kunci: Gunakan v-if untuk percabangan kondisional umum, gunakan v-show jika elemen sangat sering di-toggle.
15. 🟢 List Rendering
List rendering adalah teknik untuk merender daftar elemen berulang (seperti daftar produk, daftar pengguna, atau baris tabel) dari data Array atau Objek ke template HTML menggunakan directive v-for.
<script setup>
const users = [
{ id: 1, name: 'Budi' },
{ id: 2, name: 'Andi' },
{ id: 3, name: 'Siti' }
]
</script>
<template>
<ul>
<li v-for="user in users" :key="user.id">
{{ user.name }}
</li>
</ul>
</template>Output:
Budi
Andi
SitiDiagram:
users
│
├── Budi
├── Andi
└── Siti
│
▼
v-for
│
▼
<li>...</li>Sangat penting: key
:key="user.id"Selalu sertakan atribut :key yang unik dan stabil (seperti ID database) pada setiap item v-for. Atribut key membantu algoritma Virtual DOM Vue melacak identitas setiap elemen secara efisien saat urutan data berubah, ditambah, atau dihapus.
Kunci: Gunakan v-for="item in items" :key="item.id" untuk merender data list berulang.
16. 🟢 Event Handling
Event handling adalah mekanisme untuk mendengarkan (listen) dan merespons aksi atau interaksi dari pengguna (seperti klik tombol, input keyboard, hover mouse, atau submit form) menggunakan directive v-on atau shorthand @.
<script setup>
function sayHello() {
alert('Halo!')
}
</script>
<template>
<button @click="sayHello">
Klik
</button>
</template>Event Object
Handler fungsi dapat menerima parameter objek event bawaan browser secara otomatis:
<button @click="handleClick">
Lihat Target
</button>function handleClick(event) {
console.log(event.target)
}Event Modifier
Vue menyediakan modifier event untuk mempermudah penanganan perilaku default event tanpa perlu memanggil event.preventDefault() atau event.stopPropagation() secara manual:
<!-- Mencegah reload halaman saat submit form -->
<form @submit.prevent="submit"><!-- Menghentikan event bubbling -->
<button @click.stop="click">Modifier umum:
.prevent → memanggil event.preventDefault()
.stop → memanggil event.stopPropagation()
.self → hanya trigger jika target event adalah elemen itu sendiri
.once → hanya trigger event 1 kali saja
.capture → menggunakan event capture modeKey modifier untuk mendengarkan tombol keyboard tertentu:
<input @keyup.enter="submit">Kunci: Gunakan @event="handler" untuk menangani event interaksi pengguna.
17. 🟢 Input Binding
Input binding adalah mekanisme untuk menghubungkan nilai elemen form input (seperti input teks, textarea, checkbox, radio, dan select dropdown) dengan reactive state secara dua arah (two-way data binding) menggunakan directive v-model.
Ketika pengguna mengetik di form UI, data state otomatis berubah. Sebaliknya, ketika data state diubah lewat kode JavaScript, tampilan form di UI otomatis diperbarui.
<script setup>
import { ref } from 'vue'
const name = ref('')
</script>
<template>
<input v-model="name">
<p>Halo {{ name }}</p>
</template>Jika user mengetik:
BudiOutput:
Halo BudiDiagram:
User mengetik di UI
│
▼
v-model (Two-way Binding)
│
▼
name.value (State)
│
▼
Template / DOM terupdatePenggunaan pada Berbagai Tipe Input
v-model secara cerdas menyesuaikan properti DOM yang di-bind sesuai tipe inputnya:
<!-- Input Text -->
<input v-model="text">
<!-- Checkbox (boolean) -->
<input type="checkbox" v-model="checked">
<!-- Select Dropdown -->
<select v-model="selected">
<option value="A">Opsi A</option>
<option value="B">Opsi B</option>
</select>Kunci: v-model menyederhanakan two-way data binding antara form input dan reactive state.
18. 🟡 Watchers
watch() digunakan untuk menjalankan side effect ketika state berubah.
<script setup>
import { ref, watch } from 'vue'
const count = ref(0)
watch(count, (newValue, oldValue) => {
console.log(oldValue, '→', newValue)
})
count.value = 1Konsep:
count berubah
↓
watch mendeteksi
↓
callback dijalankanGunakan watcher untuk hal seperti:
API request
localStorage
logging
side effectJangan gunakan watch hanya untuk menghitung nilai turunan. Untuk itu gunakan computed.
Kunci:
computed = menghasilkan nilai baru berdasarkan data lain.
watch = melakukan sesuatu ketika data berubah.19. 🟡 Template Refs
Template ref digunakan untuk mendapatkan referensi langsung ke elemen HTML (DOM) atau instance komponen anak tanpa harus menggunakan document.querySelector().
<script setup>
import { ref, onMounted } from 'vue'
// Inisialisasi ref dengan nilai awal null
const input = ref(null)
onMounted(() => {
// DOM element sudah siap dan dapat diakses
input.value.focus()
})
</script>
<template>
<!-- Menghubungkan variabel ref dengan elemen HTML -->
<input ref="input" placeholder="Otomatis fokus saat halaman dimuat">
</template>Diagram:
Deklarasi di <script>
const input = ref(null) ── (awal: null)
│
│ template rendering
▼
<input ref="input"> ─────── (elemen DOM terpasang)
│
│ onMounted()
▼
input.value ───────────────> HTMLInputElement (bisa di-focus(), dll.)Mengapa Nilai Awalnya null?
Saat baris <script setup> pertama kali dieksekusi, komponen baru saja diinisialisasi dan elemen HTML belum dibuat di DOM fisik. Oleh karena itu, variabel ref harus diinisialisasi dengan null.
Referensi DOM baru akan terisi oleh Vue setelah komponen selesai dirender dan dipasang ke DOM (onMounted()).
Kunci: Template ref hanya dapat diakses setelah komponen masuk ke tahap onMounted().
20. 🟡 Lifecycle Hooks
Lifecycle adalah tahapan hidup sebuah component Vue, mulai dari component dibuat, ditampilkan, diperbarui, sampai dihapus.
Yang sering digunakan:
onMounted
onUpdated
onUnmountedContoh:
<script setup>
import {
onMounted,
onUpdated,
onUnmounted
} from 'vue'
onMounted(() => {
console.log('Mounted')
})
onUpdated(() => {
console.log('Updated')
})
onUnmounted(() => {
console.log('Unmounted')
})
</script>Diagram:
┌───────────────────┐
│ Component dibuat │
└─────────┬─────────┘
│
▼
┌──────────────┐
│ onMounted() │
│ │
│ masuk DOM │
└─────┬────────┘
│
│ Component Hidup
▼
┌─────────────────────────┐
│ State berubah │
│ │ │
│ ▼ │
│ onUpdated() │
│ │ │
│ │ │
│ State berubah lagi │
│ │ │
│ ▼ │
│ onUpdated() │
│ │ │
│ ... │
└─────────┬───────────────┘
│
│ Component dihapus
▼
┌──────────────────┐
│ onUnmounted() │
│ │
│ setelah dilepas │
└──────────────────┘Kunci:
onMounted → setelah component masuk DOM
onUpdated → setelah component/DOM diperbarui
dan bisa terjadi berkali-kali
onUnmounted → setelah component dilepasUrutan sederhananya:
Component dibuat
│
▼
onMounted()
│
▼
Component hidup
│
├──── state berubah ────→ onUpdated()
│ │
│ └── bisa berulang
│
▼
Component dihapus
│
▼
onUnmounted()Referensi: Lifecycle Hooks & Lifecycle Diagram
21. 🟡 Component
Component adalah bagian UI yang dapat digunakan kembali.
UserCard.vue:
<script setup>
defineProps({
name: String
})
</script>
<template>
<div>
User: {{ name }}
</div>
</template>Parent:
<script setup>
import UserCard from './components/UserCard.vue'
</script>
<template>
<UserCard name="Budi" />
<UserCard name="Andi" />
</template>Output:
User: Budi
User: AndiDiagram:
App
│
├── UserCard("Budi")
│
└── UserCard("Andi")22. 🟡 Component Props
Props adalah data yang dikirim parent → child.
Child:
<script setup>
defineProps({
name: String,
age: Number
})
</script>
<template>
<p>{{ name }} - {{ age }}</p>
</template>Parent:
<UserCard
name="Budi"
:age="20"
/>Output:
Budi - 20Dengan TypeScript
<script setup lang="ts">
interface Props {
name: string
age: number
}
defineProps<Props>()
</script>Diagram:
Parent
│
│ props
▼
ChildKunci: props = input component.
23. 🟡 Component Event
Component Event digunakan untuk komunikasi dari child component ke parent component melalui custom event (emit).
Child:
<script setup>
const emit = defineEmits(['save'])
function save() {
emit('save', 'Data dari child')
}
</script>
<template>
<button @click="save">
Save
</button>
</template>Parent:
<script setup>
function handleSave(data) {
console.log(data)
}
</script>
<template>
<Child @save="handleSave" />
</template>Ketika button diklik:
Child
│
│
│ emit('save', data)
▼
┌───────────────────────┐
│ Event "save" + data │
└────────┬──────────────┘
│
▼
Parent
│
│ @save
▼
handleSave(data)Hubungan dengan props:
┌──────────────┐
│ Parent │
└──────┬───────┘
│ ▲
props │ │ emit event
▼ │
┌──────────────┐
│ Child │
└──────────────┘Lebih Sederhananya:
props
│
│ data
▼
Parent ─────────→ Child
emit
│
│ event + data
▼
Child ──────────→ ParentKunci:
props → parent ke child
emit → child ke parent24. 🟡 Component Model
v-model pada komponen kustom digunakan untuk membuat komunikasi dua arah (two-way data binding) antara parent dan child component.
1. Cara Modern: defineModel() (Vue 3.4+)
defineModel() adalah makro resmi bawaan Vue yang menyederhanakan two-way binding menjadi sangat ringkas:
Child Component (CustomInput.vue):
<script setup>
// defineModel() otomatis mengelola prop dan emit internal
const model = defineModel()
</script>
<template>
<input v-model="model" placeholder="Ketik sesuatu...">
</template>Parent Component:
<script setup>
import { ref } from 'vue'
import CustomInput from './CustomInput.vue'
const username = ref('Budi')
</script>
<template>
<!-- v-model sinkron otomatis 2 arah dengan CustomInput -->
<CustomInput v-model="username" />
<p>Nama: {{ username }}</p>
</template>Output:
Nama: Budi2. Cara Konvensional (Vue 3.3 ke Bawah)
Di balik layar, v-model pada komponen bekerja menggunakan pasangan prop modelValue dan event update:modelValue:
Child Component (UserInput.vue):
<script setup>
defineProps({
modelValue: String
})
const emit = defineEmits(['update:modelValue'])
</script>
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
>
</template>Diagram Alur Kerja:
Parent (username)
│
│ :modelValue (Prop ke bawah)
▼
Child (<input>)
│
│ @update:modelValue (Emit ke atas)
▼
Parent (username terupdate)Kunci:
Vue 3.4+ → gunakan defineModel() (paling bersih dan direkomendasikan)
Di balik layar → prop modelValue + emit update:modelValue25. 🟡 Fallthrough Attributes
Fallthrough attribute adalah atribut atau event listener yang dikirimkan ke komponen dari parent, tetapi tidak dideklarasikan secara eksplisit di defineProps() atau defineEmits().
Secara default, atribut-atribut ini akan otomatis diteruskan (fallthrough) ke elemen root dari komponen anak.
Child Component (MyButton.vue):
<template>
<!-- class, id, style, dan event listener akan otomatis jatuh ke root <button> -->
<button class="btn">
Simpan
</button>
</template>Parent Component:
<template>
<MyButton
id="save-btn"
class="btn-primary"
disabled
@click="handleClick"
/>
</template>Output di DOM:
<button id="save-btn" class="btn btn-primary" disabled>Simpan</button>Menonaktifkan Fallthrough: inheritAttrs: false
Jika Anda tidak ingin atribut otomatis jatuh ke root element, matikan fitur ini menggunakan defineOptions:
<script setup>
defineOptions({
inheritAttrs: false
})
</script>
<template>
<div class="wrapper">
<!-- Tempelkan atribut secara eksplisit ke elemen yang diinginkan menggunakan $attrs -->
<button v-bind="$attrs">Tombol Kustom</button>
</div>
</template>Multi-Root Components (Fragments)
Jika komponen anak memiliki lebih dari satu elemen root di <template>, Vue tidak tahu elemen mana yang harus menerima atribut fallthrough dan akan menampilkan peringatan di konsol.
Solusinya: gunakan v-bind="$attrs" pada elemen yang dituju secara eksplisit:
<!-- Multi-root: wajib tentukan elemen target dengan v-bind="$attrs" -->
<template>
<header>Header</header>
<main v-bind="$attrs">Konten Utama</main>
<footer>Footer</footer>
</template>Kunci: Fallthrough attribute otomatis jatuh ke single-root element; gunakan v-bind="$attrs" untuk kontrol manual atau pada multi-root components.
26. 🟡 Component Slot
Slot digunakan untuk mengirim isi/template dari parent ke child.
Child:
<template>
<div class="card">
<slot />
</div>
</template>Parent:
<Card>
<h2>Hello</h2>
<p>Isi card</p>
</Card>Hasil konsep:
Card
┌──────────────────┐
│ Hello │
│ Isi card │
└──────────────────┘Named slot
Child:
<template>
<header>
<slot name="header" />
</header>
<main>
<slot />
</main>
</template>Parent:
<Card>
<template #header>
<h1>Judul</h1>
</template>
<p>Isi</p>
</Card>Kunci:
props → kirim data
slot → kirim template/isi UI27. 🟡 Dynamic Component
Dynamic component digunakan untuk mengganti component secara dinamis.
<script setup>
import { ref } from 'vue'
import Home from './Home.vue'
import About from './About.vue'
const current = ref(Home)
</script>
<template>
<button @click="current = Home">Home</button>
<button @click="current = About">About</button>
<component :is="current" />
</template>Diagram:
current
│
├── Home
│ ↓
│ <component :is="current">
│
└── About
↓
component berubahCocok untuk:
tab
wizard
step
dynamic view28. 🟡 Provide dan Inject
provide dan inject digunakan untuk mengirimkan data dari komponen leluhur (ancestor/grandparent) langsung ke komponen keturunan (descendant/grandchild) yang berada jauh di dalam component tree tanpa harus meneruskan props secara manual di setiap level (menghindari prop drilling).
Ancestor / Parent Component:
<script setup>
import { provide, ref } from 'vue'
// Menyediakan reactive state
const theme = ref('dark')
function toggleTheme() {
theme.value = theme.value === 'dark' ? 'light' : 'dark'
}
// provide(key, value)
provide('theme', theme)
provide('toggleTheme', toggleTheme)
</script>
<template>
<ChildComponent />
</template>Descendant / Grandchild Component:
<script setup>
import { inject } from 'vue'
// inject(key, defaultValue)
const theme = inject('theme')
const toggleTheme = inject('toggleTheme')
</script>
<template>
<p>Tema Saat Ini: {{ theme }}</p>
<button @click="toggleTheme">Ganti Tema</button>
</template>Diagram:
Ancestor Component
│
│ provide('theme', ref('dark'))
▼
Child Component (tidak perlu tahu/meneruskan prop theme)
│
▼
Grandchild Component
│
│ inject('theme') ──> otomatis reactive!
▼
UI TerupdateReaktivitas pada Provide/Inject
- Jika data yang di-provide dibungkus dengan
ref()ataureactive(), koneksi reaktivitas tetap terjaga. Ketika nilaitheme.valuedi ancestor berubah, komponen anak yang meng-inject data tersebut akan otomatis terupdate. - Best Practice: Jika komponen anak perlu mengubah data yang di-inject, sediakan fungsi mutator dari ancestor (seperti
toggleThemedi atas) dan jangan mengubah nilaitheme.valuesecara langsung di komponen anak.
Kunci:
props → komunikasi langsung parent → child
provide → ancestor menyediakan data/fungsi ke level bawah
inject → descendant mengambil data/fungsi dari ancestor29. 🔴 Component Instance
Component instance adalah object internal yang mewakili sebuah instance component.
Component
│
▼
Component Instance
(object component yang sedang berjalan)Dalam Composition API, biasanya kita tidak perlu mengakses instance secara langsung.
Namun untuk kasus tertentu:
<script setup>
import { getCurrentInstance } from 'vue'
const instance = getCurrentInstance()
console.log(instance)
</script>getCurrentInstance() hanya digunakan dalam konteks setup tertentu dan umumnya tidak diperlukan untuk penggunaan Vue sehari-hari.
Untuk expose API dari child ke parent: Child dapat menentukan function/property yang boleh diakses Parent:
<script setup>
function focus() {
console.log('focus')
}
defineExpose({
focus
})
</script>Parent dapat menggunakan template ref pada component untuk mengakses API yang diexpose.
Diagram:
Parent
│
│ template ref
▼
Child instance
│
│ exposed API
▼
focus()Kunci:
Component Instance → object yang mewakili component
defineExpose() → menentukan API Child yang boleh diakses Parent
template ref → Parent mengakses API ChildCatatan: jangan menjadikan akses instance sebagai cara utama komunikasi antar-component. Gunakan props, emits, slots, provide/inject, atau state management sesuai kebutuhan.
30. 🧠 Peta Ingatan Cepat
A. Alur dasar Vue
STATE
│
▼
TEMPLATE
│
▼
DOM
│
│ user interaction
▼
EVENT
│
│ mengubah
▼
STATE
│
└───────────────┐
│
reactive
│
▼
TEMPLATEContoh:
count = 0
│
▼
{{ count }}
│
▼
Count: 0
│
click
▼
count++
│
▼
Count: 1Kunci:
State → Template → DOM
Event → State berubah → Template updateB. State
ref()
↓
nilai reactive
↓
.value di JavaScript
↓
.value diakses langsung di template
reactive()
↓
object reactive
↓
property langsungContoh:
ref('Budi')
↓
name.value
reactive({ name: 'Budi' })
↓
user.nameKunci:
ref → nilai tunggal / reference
reactive → objectC. Template
{{ value }}
↓
interpolation
↓
menampilkan nilai
:value
↓
v-bind
↓
binding attribute/property
@click
↓
v-on
↓
event handler
v-if
↓
conditional rendering
v-for
↓
list rendering
v-model
↓
two-way bindingKunci:
{{ }} → tampilkan data
: → binding
@ → event
v-if → kondisi
v-for → perulangan
v-model → two-way bindingD. Computed vs Watch
Butuh MENGHASILKAN NILAI?
│
▼
computed
Butuh MENJALANKAN SIDE EFFECT?
│
▼
watchContoh computed:
price + quantity
│
▼
computed
│
▼
totalContoh watch:
user berubah
│
▼
watch
│
▼
API / localStorage / loggingKunci:
computed → menghasilkan nilai
watch → menjalankan side effectE. Component Communication
┌──────────────┐
│ Parent │
└──────┬───────┘
│ ▲
props │ │ emit event
▼ │
┌──────────────┐
│ Child │
└──────┬───────┘
│
slot │
▼
┌──────────────┐
│ UI Content │
└──────────────┘Kunci:
props → parent → child
emit → child → parent
slot → parent mengirim template/content ke childF. Provide / Inject
Ancestor
│
│ provide()
▼
Parent
│
▼
Child
│
▼
Descendant
│
│ inject()
▼
gunakan dataDigunakan ketika data perlu digunakan oleh descendant yang jauh, sehingga tidak perlu meneruskan props melalui setiap level (prop drilling). Kunci:
provide → menyediakan data ke bawah
inject → mengambil data dari ancestorG. Component Dynamic
component
│
┌──────┼──────┐
▼ ▼ ▼
Home About Profile
▲
│
:is="current"
│
▼
component yang aktifContoh:
<component :is="current" />Jika:
current = Home
↓
menampilkan Home
current = About
↓
menampilkan AboutKunci:
:is → menentukan component yang sedang ditampilkan31. 📚 Tabel Ringkasan
| Materi | Fungsi | Kata kunci |
|---|---|---|
| Pengenalan | Dasar Vue | component, reactive |
| Membuat Project | Membuat aplikasi | Vite, npm |
| Hello Vue | Component pertama | <template> |
| API Style | Gaya penulisan Vue | Composition / Options |
| Template | Menulis UI | {{ }} |
| Template Attributes | Binding attribute | : / v-bind |
| JS Expression | Ekspresi di template | {{ }} |
| Directive | Instruksi Vue | v- |
| State | Data UI | ref, reactive |
| DOM Update | Update UI | reactive update |
| Reactive | Data reaktif | ref, reactive |
| Computed | Nilai turunan | computed() |
| Style | Dynamic styling | :class, :style |
| Conditional | Kondisi | v-if, v-show |
| List | Perulangan | v-for, :key |
| Event | Interaksi | @click |
| Input | Form binding | v-model |
| Watchers | Side effect | watch() |
| Template Refs | Akses DOM/component | ref="..." |
| Lifecycle | Siklus component | onMounted() |
| Component | UI reusable | .vue |
| Props | Parent → child | defineProps() |
| Event | Child → parent | defineEmits() |
| Model | Component v-model | defineModel() |
| Fallthrough | Attribute ke root | $attrs |
| Slot | Kirim template | <slot> |
| Dynamic Component | Component dinamis | <component :is> |
| Provide | Kirim data ke bawah | provide() |
| Inject | Ambil data dari atas | inject() |
| Component Instance | Instance component | getCurrentInstance() |
32. ⚡ Cheat Code Vue 10 Detik
ref()membuat state reactive.reactive()membuat object reactive.computed()membuat nilai turunan.watch()mengawasi perubahan untuk side effect.v-ifmengatur kondisi.v-formembuat list.@menangani event.:melakukan binding.v-modelmenghubungkan input dengan state. Props mengirim data parent → child. Emit mengirim event child → parent. Slot mengirim template. Provide/Inject mengirim data melewati component tree.
33. 🧭 Urutan Belajar yang Disarankan
1. Pengenalan
↓
2. Membuat Project
↓
3. Hello Vue
↓
4. Template
↓
5. Directive
↓
6. State
↓
7. Reactive
↓
8. Computed
↓
9. Conditional + List
↓
10. Event + Input
↓
11. Watch
↓
12. Component
↓
13. Props + Event
↓
14. Slot
↓
15. Component Model
↓
16. Lifecycle
↓
17. Provide / Inject
↓
18. Dynamic Component
↓
19. Component Instance34. 🏗️ Mini Project untuk Menggabungkan Konsep
Contoh sederhana Todo App:
<script setup>
import { ref, computed } from 'vue'
const newTodo = ref('')
const todos = ref([
{ id: 1, text: 'Belajar Vue', done: false },
{ id: 2, text: 'Membuat project', done: true }
])
const remaining = computed(() => {
return todos.value.filter(todo => !todo.done).length
})
function addTodo() {
if (!newTodo.value.trim()) return
todos.value.push({
id: Date.now(),
text: newTodo.value,
done: false
})
newTodo.value = ''
}
function removeTodo(id) {
todos.value = todos.value.filter(todo => todo.id !== id)
}
</script>
<template>
<h1>Todo App</h1>
<form @submit.prevent="addTodo">
<input
v-model="newTodo"
placeholder="Todo baru..."
>
<button>Tambah</button>
</form>
<p>Sisa: {{ remaining }}</p>
<ul>
<li
v-for="todo in todos"
:key="todo.id"
>
<input
type="checkbox"
v-model="todo.done"
>
<span :class="{ done: todo.done }">
{{ todo.text }}
</span>
<button @click="removeTodo(todo.id)">
Hapus
</button>
</li>
</ul>
</template>
<style>
.done {
text-decoration: line-through;
}
</style>Output konsep:
Todo App
[ Todo baru... ] [Tambah]
Sisa: 1
☐ Belajar Vue [Hapus]
☑ Membuat project [Hapus]Project kecil ini sudah menggabungkan:
ref
│
├── state
│
├── computed
│
├── v-model
│
├── v-for
│
├── :class
│
├── @click
│
└── @submit.preventKunci: pahami alur State → Template → Event → State berubah → DOM diperbarui. Setelah itu pelajari Component → Props → Emit → Slot, karena sebagian besar aplikasi Vue tersusun dari pola tersebut.