Vue 3 Pinia vs Vuex: Panduan Lengkap State Management dan Pertanyaan Interview 2026

Perbandingan mendalam Pinia vs Vuex: arsitektur, TypeScript support, Composition API, performa, strategi migrasi, dan pertanyaan interview Vue state management 2026.

Diagram perbandingan state management Vue 3 Pinia vs Vuex

Ekosistem Vue 3 mengalami transformasi signifikan dalam pengelolaan state dengan hadirnya Pinia sebagai solusi resmi yang menggantikan Vuex. Pinia dirancang dari awal untuk memanfaatkan Composition API, memberikan type safety yang superior, dan menghilangkan boilerplate code yang sering ditemukan di Vuex. Artikel ini mengupas perbedaan arsitektur kedua library, pola migrasi praktis, dan pertanyaan interview yang relevan untuk tahun 2026.

Poin Kunci

Pinia menghilangkan konsep mutations sepenuhnya, mengurangi bundle size hingga 83%, dan memberikan full TypeScript inference tanpa konfigurasi tambahan. Untuk proyek Vue 3 baru di tahun 2026, Pinia adalah pilihan standar yang direkomendasikan official Vue team.

Perbedaan Arsitektur Inti

Vuex 4 dan Pinia 3 memiliki filosofi desain yang berbeda. Vuex menggunakan single store dengan nested modules, memerlukan mutations terpisah untuk state changes, dan dibangun berdasarkan Options API. Sebaliknya, Pinia mengadopsi pendekatan multiple flat stores, memungkinkan direct state mutations, dan native support untuk Composition API.

| Feature | Pinia 3 | Vuex 4 | |---------|---------|--------| | Mutations | None (direct state changes) | Required for state changes | | TypeScript | Full inference, no augmentation | Manual type augmentation needed | | Store architecture | Multiple flat stores | Single store with nested modules | | Composition API | Native support | Options API based | | Bundle size | ~1 KB gzipped | ~6 KB gzipped | | Vue Devtools | Full support (v7) | Full support | | SSR | Built-in | Requires configuration | | Hot Module Replacement | Built-in | Manual setup |

Arsitektur flat stores di Pinia memudahkan code splitting dan tree shaking, sementara eliminasi mutations mengurangi kompleksitas mental model. Developer tidak perlu lagi membedakan kapan menggunakan commit vs dispatch, karena semua state changes dilakukan langsung di dalam actions.

Mendefinisikan Store: Options API vs Setup Syntax

Pinia menawarkan dua sintaks untuk mendefinisikan store, keduanya dengan trade-off yang berbeda. Options Store API familiar bagi developer Vuex, sementara Setup Store memberikan fleksibilitas penuh Composition API.

stores/counter-options.tstypescript
import { defineStore } from 'pinia'

// Options Store syntax — familiar to Vuex developers
export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
    lastUpdated: null as Date | null,
  }),
  getters: {
    // Getters receive state as first argument with full type inference
    doubleCount: (state) => state.count * 2,
    isPositive(): boolean {
      // Access other getters via `this`
      return this.count > 0
    },
  },
  actions: {
    increment() {
      // Direct state mutation — no commit() needed
      this.count++
      this.lastUpdated = new Date()
    },
    async fetchCount(id: string) {
      // Async actions work without extra configuration
      const response = await fetch(`/api/counters/${id}`)
      const data = await response.json()
      this.count = data.count
    },
  },
})

Setup Store memberikan kontrol penuh dengan menggunakan Composition API pattern yang sama seperti di component <script setup>:

stores/counter-setup.tstypescript
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

// Setup Store syntax — identical patterns to Composition API
export const useCounterStore = defineStore('counter', () => {
  // ref() becomes state
  const count = ref(0)
  const lastUpdated = ref<Date | null>(null)

  // computed() becomes getters
  const doubleCount = computed(() => count.value * 2)
  const isPositive = computed(() => count.value > 0)

  // Functions become actions
  function increment() {
    count.value++
    lastUpdated.value = new Date()
  }

  async function fetchCount(id: string) {
    const response = await fetch(`/api/counters/${id}`)
    const data = await response.json()
    count.value = data.count
  }

  // Must return all state, getters, and actions
  return { count, lastUpdated, doubleCount, isPositive, increment, fetchCount }
})

Setup syntax memungkinkan penggunaan composables Vue langsung di dalam store definition, memberikan fleksibilitas untuk mengintegrasikan third-party libraries seperti VueUse tanpa wrapper khusus.

Integrasi TypeScript

Salah satu keunggulan terbesar Pinia adalah type inference otomatis yang bekerja tanpa konfigurasi manual. Vuex 4 memerlukan module augmentation untuk mendapatkan type safety:

typescript
// Vuex 4 — manual type augmentation required
import { Store } from 'vuex'

declare module 'vuex' {
  interface State {
    counter: {
      count: number
      lastUpdated: Date | null
    }
  }
}

// Accessing state requires type assertions or custom helpers
const count = (store.state as { counter: { count: number } }).counter.count

Pinia menghilangkan kompleksitas ini sepenuhnya:

typescript
// Pinia — full type inference, zero configuration
const counter = useCounterStore()

// TypeScript knows counter.count is number
// TypeScript knows counter.doubleCount is number
// TypeScript knows counter.increment() returns void
// TypeScript knows counter.fetchCount() returns Promise<void>
counter.increment()
console.log(counter.doubleCount)

Type inference bekerja end-to-end, dari state definition hingga component consumption. IDE autocomplete dan error checking berfungsi sempurna tanpa type assertions atau generic constraints manual.

Composition API dan Integrasi Composable

Pinia dirancang untuk bekerja seamless dengan Vue composables, memungkinkan reusable logic langsung di dalam store definition. Contoh berikut menggunakan VueUse untuk debouncing dan Vue Router untuk URL sync:

stores/search.tstypescript
import { defineStore } from 'pinia'
import { ref, watch } from 'vue'
import { useDebounceFn } from '@vueuse/core'
import { useRoute } from 'vue-router'

export const useSearchStore = defineStore('search', () => {
  const route = useRoute()
  const query = ref('')
  const results = ref<SearchResult[]>([])
  const isLoading = ref(false)

  // VueUse composable used directly inside the store
  const debouncedSearch = useDebounceFn(async (term: string) => {
    if (!term.trim()) {
      results.value = []
      return
    }
    isLoading.value = true
    try {
      const res = await fetch(`/api/search?q=${encodeURIComponent(term)}`)
      results.value = await res.json()
    } finally {
      isLoading.value = false
    }
  }, 300)

  // Watcher syncs URL query param to store state
  watch(() => route.query.q, (q) => {
    if (typeof q === 'string') {
      query.value = q
      debouncedSearch(q)
    }
  })

  function setQuery(term: string) {
    query.value = term
    debouncedSearch(term)
  }

  return { query, results, isLoading, setQuery }
})

Pattern ini memungkinkan business logic yang kompleks tetap berada di dalam store layer, sementara memanfaatkan ecosystem Vue composables yang sudah matang. Developer tidak perlu menulis custom plugins atau middleware untuk kebutuhan seperti debouncing, throttling, atau state persistence.

Siap menguasai wawancara Vue.js / Nuxt.js Anda?

Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.

Komunikasi Antar Store dan Cross-Store References

Pinia memudahkan komunikasi antar store dengan memanggil store lain sebagai composable. Tidak ada namespace atau module paths yang perlu dikelola:

stores/cart.tstypescript
import { defineStore } from 'pinia'
import { useProductStore } from './products'
import { useAuthStore } from './auth'
import { ref, computed } from 'vue'

export const useCartStore = defineStore('cart', () => {
  const items = ref<CartItem[]>([])

  const total = computed(() => {
    // Access another store by calling its composable
    const productStore = useProductStore()
    return items.value.reduce((sum, item) => {
      const product = productStore.getById(item.productId)
      return sum + (product?.price ?? 0) * item.quantity
    }, 0)
  })

  async function checkout() {
    const auth = useAuthStore()
    if (!auth.isAuthenticated) {
      throw new Error('Authentication required')
    }
    // Checkout logic
  }

  return { items, total, checkout }
})

Pendekatan ini memberikan dependency graph yang eksplisit dan mudah di-trace. TypeScript akan warning jika terjadi circular dependency, dan hot module replacement tetap bekerja dengan benar karena setiap store adalah module terpisah.

Migrasi dari Vuex 4 ke Pinia 3

Proses migrasi dari Vuex ke Pinia mengikuti pola yang konsisten. Berikut contoh transformasi module Vuex menjadi Pinia store:

Kompatibilitas Migrasi

Pinia dan Vuex dapat berjalan bersamaan dalam satu aplikasi. Install Pinia bersamaan dengan Vuex, migrasikan satu module per waktu, dan hapus Vuex setelah semua modules dikonversi.

typescript
// Before: Vuex module
// store/modules/user.ts
const userModule = {
  namespaced: true,
  state: () => ({
    profile: null as UserProfile | null,
    preferences: {} as UserPreferences,
  }),
  mutations: {
    SET_PROFILE(state, profile: UserProfile) {
      state.profile = profile
    },
    UPDATE_PREFERENCES(state, prefs: Partial<UserPreferences>) {
      state.preferences = { ...state.preferences, ...prefs }
    },
  },
  actions: {
    async fetchProfile({ commit }) {
      const profile = await api.getProfile()
      commit('SET_PROFILE', profile)
    },
    async updatePreferences({ commit }, prefs: Partial<UserPreferences>) {
      await api.updatePreferences(prefs)
      commit('UPDATE_PREFERENCES', prefs)
    },
  },
  getters: {
    isLoggedIn: (state) => state.profile !== null,
    displayName: (state) => state.profile?.name ?? 'Guest',
  },
}

Setelah migrasi ke Pinia:

typescript
// After: Pinia store
// stores/user.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { api } from '@/lib/api'

export const useUserStore = defineStore('user', () => {
  const profile = ref<UserProfile | null>(null)
  const preferences = ref<UserPreferences>({})

  const isLoggedIn = computed(() => profile.value !== null)
  const displayName = computed(() => profile.value?.name ?? 'Guest')

  async function fetchProfile() {
    profile.value = await api.getProfile()
  }

  async function updatePreferences(prefs: Partial<UserPreferences>) {
    await api.updatePreferences(prefs)
    Object.assign(preferences.value, prefs)
  }

  return { profile, preferences, isLoggedIn, displayName, fetchProfile, updatePreferences }
})

Perubahan kunci mencakup eliminasi mutations layer, penggunaan direct state assignment, dan transformasi getters menjadi computed properties. Component consumption berubah dari mapState, mapGetters, mapActions menjadi direct store instance usage.

SSR State Hydration dengan Pinia dan Nuxt 4

Pinia menyediakan built-in support untuk server-side rendering dengan state hydration otomatis. Pada Nuxt 4, Pinia terintegrasi natively tanpa konfigurasi tambahan:

Perhatian SSR

Setup stores yang menggunakan composables seperti useRoute() atau useFetch() memerlukan penanganan khusus dalam konteks SSR. Composables ini hanya boleh dipanggil selama fase setup, tidak di dalam async callbacks.

stores/products.ts — SSR-safe Pinia store for Nuxt 4typescript
import { defineStore } from 'pinia'

export const useProductStore = defineStore('products', {
  state: () => ({
    items: [] as Product[],
    selectedCategory: 'all',
  }),
  actions: {
    async fetchProducts() {
      // useFetch is Nuxt-specific — use $fetch for store actions
      this.items = await $fetch<Product[]>('/api/products', {
        query: { category: this.selectedCategory },
      })
    },
  },
  getters: {
    getById: (state) => (id: string) =>
      state.items.find((item) => item.id === id),
    filteredCount: (state) =>
      state.items.filter((p) => p.inStock).length,
  },
})

State yang diisi di server otomatis di-serialize dan dikirim ke client, kemudian di-hydrate saat aplikasi boot di browser. Nuxt Pinia module menangani serialization, hydration, dan garbage collection secara otomatis. Developer hanya perlu memastikan state objects serializable dan tidak mengandung functions atau class instances.

Perbandingan Performa

Dari sisi bundle size, Pinia jauh lebih ringan. Runtime overhead Pinia sekitar 1 KB gzipped dibanding Vuex 6 KB gzipped, pengurangan sebesar 83%. Perbedaan ini signifikan untuk aplikasi dengan banyak stores atau environments dengan bandwidth terbatas.

Reactive updates di Pinia sedikit lebih cepat karena eliminasi mutations layer. Setiap state change di Vuex melewati mutation handler sebelum mencapai reactivity system, menambah overhead minimal namun measurable pada high-frequency updates. Pinia langsung memicu reactivity tanpa intermediary layer.

DevTools performance juga lebih baik di Pinia. Vue DevTools 7 dapat tree-shake stores yang tidak aktif, sementara Vuex single store architecture membuat seluruh state tree tetap observable bahkan saat tidak digunakan. Pada aplikasi besar dengan 20+ modules, perbedaan memory footprint bisa mencapai 15-20%.

Hot Module Replacement di Pinia bekerja per-store basis. Perubahan pada satu store tidak memicu reload stores lain, mempercepat development iteration. Vuex memerlukan manual HMR setup untuk behavior serupa.

Pertanyaan Interview yang Sering Muncul

Q: Mengapa Pinia menghilangkan mutations? Bukankah itu melanggar prinsip Flux architecture?

Mutations di Vuex diciptakan untuk strict mode debugging dan time-travel debugging. Namun, Vue DevTools modern dapat track state changes bahkan tanpa mutations layer. Pinia tetap mendukung DevTools timeline dan state inspection, sementara mengurangi boilerplate. Direct state mutations di Pinia tetap trackable karena Pinia wraps state dengan Proxy yang merecord semua changes. Developer mendapat debugging experience yang sama tanpa overhead menulis mutations terpisah.

Q: Bagaimana Pinia menangani code splitting untuk stores? Apakah semua stores di-bundle sekaligus?

Pinia stores adalah ES modules reguler, sehingga otomatis mendukung dynamic imports dan code splitting. Jika store dipanggil dalam async component atau lazy route, bundler akan memasukkan store ke chunk terpisah. Contoh: const useAdminStore = () => import('@/stores/admin') hanya di-load saat admin dashboard diakses. Vuex single store architecture membuat code splitting lebih sulit karena root store harus terdefinisi di entry point.

Q: Apakah Pinia kompatibel dengan Vuex plugins yang sudah ada? Bagaimana strategi migrasi plugin?

Pinia memiliki plugin system sendiri yang lebih simple namun powerful. Vuex plugins tidak compatible langsung, namun konsepnya mudah ditranslate. Vuex plugin menerima store instance dan mengakses subscribe(), sementara Pinia plugin menerima context dengan store, app, dan options. Untuk persistence plugin, gunakan pinia-plugin-persistedstate. Untuk DevTools integration, Pinia built-in support sudah mencakup mayoritas use cases.

Q: Bagaimana Pinia menangani optimistic updates dan rollback pada error?

Pinia tidak menyediakan built-in rollback mechanism seperti Redux atau Vuex strict mode, namun developer dapat implement manual snapshot. Pattern yang direkomendasikan adalah menyimpan previous state sebelum mutation, kemudian restore jika API call gagal. Untuk use cases kompleks, kombinasikan dengan TanStack Query atau VueUse useAsyncState yang memiliki rollback bawaan. Pattern ini lebih eksplisit dan testable dibanding implicit rollback.

Q: Apakah Pinia mendukung store composition dan reusable store logic?

Ya, melalui composables. Developer dapat membuat reusable functions yang menerima store instance atau mengembalikan partial store definition. Contoh: usePageable(store) composable dapat menambahkan pagination logic ke store manapun. Setup Store syntax memudahkan pattern ini karena store definition adalah function biasa. Options Store juga mendukung via action composition, namun Setup Store lebih natural untuk reusable logic. Lihat modul Vue composables untuk pattern detail.

Untuk persiapan interview lebih lanjut, pelajari juga modul interview state management yang membahas state management patterns secara mendalam.

Mulai berlatih!

Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.

Kesimpulan

Migrasi dari Vuex ke Pinia membawa peningkatan signifikan dalam developer experience, type safety, dan performa runtime:

  • Eliminasi mutations mengurangi boilerplate dan mental overhead tanpa mengorbankan debuggability
  • Full TypeScript inference bekerja tanpa manual type augmentation atau generic constraints
  • Bundle size 83% lebih kecil dengan runtime overhead minimal dan tree-shaking optimal
  • Native Composition API support memungkinkan penggunaan ecosystem Vue composables langsung di stores
  • SSR hydration otomatis pada Nuxt 4 tanpa konfigurasi tambahan atau manual serialization
  • Code splitting per-store memudahkan lazy loading dan performance optimization
  • Flat store architecture menghilangkan nested module complexity dan namespace conflicts

Untuk proyek Vue 3 baru di tahun 2026, Pinia adalah pilihan default yang direkomendasikan official Vue team. Migrasi dari Vuex dapat dilakukan secara incremental, memungkinkan kedua libraries coexist selama transisi period.

Mulai berlatih!

Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.

Tag

#vue
#pinia
#vuex
#state-management
#interview

Bagikan

Artikel terkait