Pinia: State Management Global
Berbagi state antar banyak komponen dengan Pinia: defineStore (setup store), state, getters, actions, storeToRefs, dan kapan memakainya.
Kapan state perlu dibuat global?
Props dan emits cukup untuk komunikasi induk-anak. Tetapi data seperti isi keranjang belanja, pengguna yang login, atau preferensi tema dibutuhkan banyak komponen di halaman berbeda. Meneruskannya lewat props berlapis-lapis sangat merepotkan. Solusinya: sebuah store terpusat yang bisa diakses komponen mana pun.
Pinia adalah pustaka state management resmi Vue — ringan, sederhana, dan mendukung devtools.
npm install pinia
// main.js
import { createPinia } from 'pinia'
createApp(App).use(createPinia()).mount('#app')
Membuat store (setup store)
Gaya setup store sama persis dengan menulis <script setup>: ref = state, computed = getters, fungsi = actions.
// stores/keranjang.js
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useKeranjang = defineStore('keranjang', () => {
const items = ref([]) // state
const jumlah = computed(() => items.value.length) // getter
const total = computed(() => items.value.reduce((t, i) => t + i.harga, 0))
function tambah(item) { items.value.push(item) } // action
function kosongkan() { items.value = [] }
return { items, jumlah, total, tambah, kosongkan } // wajib di-return
})
Argumen pertama 'keranjang' adalah ID unik store. Apa pun yang tidak di-return bersifat privat.
Memakai store di komponen
<script setup>
import { useKeranjang } from '@/stores/keranjang'
const keranjang = useKeranjang()
</script>
<template>
<p>{{ keranjang.jumlah }} barang — Rp {{ keranjang.total }}</p>
<button @click="keranjang.tambah({ nama: 'Buku', harga: 50000 })">Beli</button>
</template>
Semua komponen yang memanggil useKeranjang() memperoleh store yang sama. Perubahan di satu tempat langsung terlihat di tempat lain.
Destructuring: pakai storeToRefs
import { storeToRefs } from 'pinia'
const keranjang = useKeranjang()
const { items, jumlah, total } = storeToRefs(keranjang) // state & getter tetap reaktif
const { tambah, kosongkan } = keranjang // action boleh langsung dipecah
Memecah state langsung (const { jumlah } = keranjang) memutus reaktivitas. Gunakan storeToRefs untuk state/getter.
Option store (gaya alternatif)
export const useHitung = defineStore('hitung', {
state: () => ({ n: 0 }),
getters: { ganda: (s) => s.n * 2 },
actions: { tambah() { this.n++ } },
})
| Konsep | Setup store | Option store |
|---|---|---|
| State | ref() | state: () => ({}) |
| Getter | computed() | getters |
| Action | fungsi | actions |
Action asinkron
async function muat() {
loading.value = true
try { items.value = await (await fetch('/api/produk')).json() }
finally { loading.value = false }
}
Kapan TIDAK perlu Pinia?
- State hanya dipakai satu komponen atau induk-anak langsung →
ref+ props/emits. - Logika yang dipakai ulang tapi state-nya harus terpisah per pemakai → composable.
- Tema/bahasa untuk satu cabang komponen → provide/inject.
Kesalahan umum
- Lupa
app.use(createPinia())→ error "getActivePinia() was called but there was no active Pinia". - Tidak me-return state/action dari setup store.
- Memasukkan semua state ke Pinia padahal cukup lokal.