Proyek Vue menggabungkan semua yang sudah kamu pelajari: komponen dengan props dan emits, data reaktif, computed, v-model, dan v-for, dibagi ke beberapa file dalam satu folder proyek. Bayangin hari pembukaan Warung Bu Sri. Dapur, papan menu, kasir, dan buku pesanan sudah dicoba satu per satu. Sekarang semuanya dipasang di satu warung, dan yang penting adalah siapa memegang apa: papan menu cuma menampilkan, kasir yang memegang buku pesanan.
🧪 Tebakan cepat: Di warung yang sudah jadi, ada tiga
MenuCarddan satu daftar keranjang. Datakeranjangsebaiknya disimpan di setiapMenuCard, atau diApp? Tebak dulu, jawabannya ada di bagian "Siapa memegang data".
Membuat proyek dengan create-vue
create-vue adalah alat resmi untuk membuat proyek Vue baru dengan Vite:
npm create vue@latest
Perintah itu bertanya nama proyek dan fitur apa saja yang dipasang: TypeScript, JSX, Router, Pinia, Vitest, dan lainnya. Fiturnya juga bisa langsung disebut sebagai flag. Ini keluaran sungguhan create-vue 3.24 untuk proyek dengan Router dan Pinia:
$ npm create vue@latest warung-bu-sri -- --router --pinia
┌ Vue.js - The Progressive JavaScript Framework
Scaffolding project in /home/sri/warung-bu-sri...
│
└ Done. Now run:
cd warung-bu-sri
npm install
npm run dev
npm run dev menyalakan server Vite, dan aplikasinya terbuka di http://localhost:5173. Struktur yang dibuat (sebagian):
warung-bu-sri/
├── index.html
├── package.json
├── vite.config.js
├── public/
└── src/
├── main.js
├── App.vue
├── assets/
├── components/
├── router/
│ └── index.js
├── stores/
│ └── counter.js
└── views/
├── HomeView.vue
└── AboutView.vue
index.htmlada di akar proyek, bukan disrc/. Isinya<div id="app"></div>dan satu<script type="module" src="/src/main.js">.src/main.jsmembuat aplikasinya:createApp(App), laluapp.use(createPinia()),app.use(router), danapp.mount('#app').components/berisi potongan yang dipakai ulang, sepertiMenuCard.vue.views/berisi halaman yang ditampilkan router.router/index.jsmemetakan path ke view:/keHomeView,/aboutkeAboutView. Folderviews/tanpa file router gak berguna, karena gak ada yang menampilkan halaman-halaman itu.stores/berisi store Pinia. Tanpa fitur Router dan Pinia, create-vue gak membuatrouter/,views/, danstores/.
Merakit Warung Bu Sri
App.vue cari, menu, keranjang, total
└── MenuCard.vue × 3 props: nama, harga, stok · emit: tambah
<!-- src/components/MenuCard.vue -->
<script setup>
defineProps({ nama: String, harga: Number, stok: Number })
defineEmits(["tambah"])
</script>
<template>
<div class="kartu">
<h3>{{ nama }}</h3>
<p>Rp{{ harga }}</p>
<button :disabled="stok === 0" @click="$emit('tambah', nama, harga)">
{{ stok === 0 ? "Habis" : "Tambah" }}
</button>
</div>
</template>
<!-- src/App.vue -->
<script setup>
import { ref, computed } from "vue"
import MenuCard from "./components/MenuCard.vue"
const cari = ref("")
const menu = ref([
{ id: 1, nama: "Nasi Goreng", harga: 15000, stok: 8 },
{ id: 2, nama: "Mie Ayam", harga: 12000, stok: 0 },
{ id: 3, nama: "Es Teh", harga: 5000, stok: 20 },
])
const keranjang = ref([])
let nextId = 1
const menuTampil = computed(() =>
menu.value.filter((m) => m.nama.toLowerCase().includes(cari.value.toLowerCase()))
)
const total = computed(() => keranjang.value.reduce((jumlah, item) => jumlah + item.harga, 0))
function tambahKeKeranjang(nama, harga) {
keranjang.value.push({ id: nextId++, nama, harga })
}
</script>
<template>
<input v-model="cari" placeholder="Cari menu" />
<MenuCard
v-for="m in menuTampil"
:key="m.id"
:nama="m.nama"
:harga="m.harga"
:stok="m.stok"
@tambah="tambahKeKeranjang"
/>
<ul>
<li v-for="item in keranjang" :key="item.id">{{ item.nama }}, Rp{{ item.harga }}</li>
</ul>
<p>Total: Rp{{ total }}</p>
</template>
| Aksi | cari |
Menu yang tampil | keranjang |
total |
|---|---|---|---|---|
| awal | "" |
Nasi Goreng, Mie Ayam (Habis), Es Teh | kosong | 0 |
| Tambah Nasi Goreng dua kali | "" |
tiga-tiganya | 2 item | 30000 |
| Tambah Es Teh | "" |
tiga-tiganya | 3 item | 35000 |
ketik es |
"es" |
Es Teh | 3 item | 35000 |
| Bagian | Pelajaran asalnya |
|---|---|
ref untuk cari, menu, keranjang |
reactive data |
menuTampil dan total |
computed properties |
v-model="cari" |
form dan v-model |
v-for dengan :key="m.id" |
list rendering |
:nama, :harga, :stok |
props |
$emit('tambah', ...) dan @tambah |
emits |
:disabled dan teks Habis |
template dan conditional rendering |
Siapa memegang data
keranjang disimpan di App, satu tempat untuk satu data. MenuCard gak memegang keranjang; dia cuma mengirim tambah bersama nama dan harganya, dan App yang menambah ke keranjang lalu menghitung total. Itu jawaban tebakannya. Kalau setiap MenuCard menyimpan keranjangnya sendiri, ada tiga keranjang terpisah, dan gak ada satu tempat pun yang tahu totalnya.
Begitu warungnya punya beberapa halaman, misalnya halaman Menu dan halaman Keranjang lewat router, keranjang pindah ke store Pinia di src/stores/, seperti di pelajaran Pinia, supaya semua halaman membaca data yang sama.
Di editor latihan
Editor latihan menjalankan satu komponen, jadi seluruh warung ditulis di satu tempat dengan MenuCard sebagai objek dan <component :is="MenuCard">. Kode Playground di Lab berisi versi itu.
Latihannya versi paling kecil dari pola yang sama: Todo App. Starter-nya sudah punya todos, input, dan addTodo yang menambah isi kolom lalu mengosongkannya. Kamu cukup menulis template-nya:
<div>
<input id="todo-input" v-model="input" />
<button id="add-btn" @click="addTodo">Tambah</button>
<ul id="todo-list">
<li v-for="(todo, i) in todos" :key="i">{{ todo }}</li>
</ul>
</div>
Pemeriksanya mengetik Belajar Vue ke #todo-input, mengklik #add-btn, lalu memastikan Belajar Vue muncul di #todo-list dan kolom ketiknya kembali kosong.
⚠️ Jebakan yang sering ketemu
- Data yang sama di banyak komponen. Keranjang di setiap
MenuCardberarti tiga keranjang. Simpan diApp(atau store), dan biarkan child mengirim event. - Total yang diubah manual.
total.value += hargadi setiap tempat gampang lupa dikurangi waktu item dihapus.computeddari isi keranjang selalu benar. - Folder
views/tanpa router. Halaman diviews/cuma tampil kalau adasrc/router/index.jsyang memetakan path ke view itu danapp.use(router)dimain.js. - Latihan: input, tombol, dan daftar. Kodenya harus memuat
v-model,v-for, dan@click;Belajar Vueharus muncul di#todo-list, dan#todo-inputharus kosong lagi setelah tombolnya diklik.
TL;DR: npm create vue@latest membuat proyek Vue dengan Vite; fitur seperti Router dan Pinia dipilih saat dibuat, lalu npm install dan npm run dev membuka aplikasinya di http://localhost:5173. index.html di akar, src/main.js membuat dan memasang aplikasinya, components/ untuk potongan yang dipakai ulang, views/ untuk halaman yang dipetakan router/index.js, dan stores/ untuk Pinia. Warung Bu Sri dirakit dari ref, computed, v-model, v-for dengan :key, props, dan emits, dengan satu pemilik untuk setiap data: App memegang keranjang, MenuCard cuma mengirim tambah.