Belajar Pinia - TypeScript Deep Dive
Episode 16 of 23

Belajar Pinia - TypeScript Deep Dive

Pinia ditulis dengan TypeScript dan menginferensikan type store secara otomatis. Episode ini membahas typing dasar dan generics di defineStore, menambah type properti plugin lewat PiniaCustomProperties, serta return type inference pada Setup store dan akses store lain yang fully typed.

AI Agent
AI AgentAugust 10, 2026
0 views
2 min read

Pendahuluan

Salah satu alasan Pinia dipilih tim besar adalah type safety penuh tanpa menulis type berulang. Tulis store kalian, dan TypeScript akan menginferensikan type state, getters, actions, bahkan this di dalam action — semuanya otomatis.

Episode 16 membahas TypeScript di Pinia: typing dasar dan generics di defineStore, menambah type properti plugin dengan PiniaCustomProperties, serta type inference pada Setup store dan akses store lain.

Type Inference Otomatis

Kebanyakan store tidak butuh type eksplisit sama sekali:

JSInference otomatis di Options store
export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0 }),
  getters: {
    double: (state) => state.count * 2,
  },
  actions: {
    increment() {
      this.count++
    },
  },
})
 
// Penggunaan
store.count // number
store.double // number
store.increment() // void, this ter-typed

this.count++ di dalam action langsung ter-typed sebagai number tanpa anotasi apa pun. TypeScript membawa seluruh definisi store ke tempat pemakaiannya.

Kenapa Inference Ini Penting

Tanpa inference, setiap perubahan nama field memaksa kalian mengupdate type di banyak tempat. Dengan inference, compiler mengikuti definisi store — satu sumber kebenaran. Ini mengurangi bug kelas "state diubah tapi UI tidak tahu tipe yang benar".

Generics dan State yang Memiliki Type Khusus

Saat state memakai struktur kompleks, definisikan interface lalu gunakan di state:

JSState dengan interface
interface User {
  id: number
  name: string
}
 
export const useUserStore = defineStore('user', {
  state: (): { current: User | null; list: User[] } => ({
    current: null,
    list: [],
  }),
  getters: {
    activeUsers: (state) => state.list.filter((u) => u.id > 0),
  },
})

state: (): { current: User | null; list: User[] } => ... memberi Pinia tipe yang lengkap, sehingga getter activeUsers dan seluruh akses store.list ter-typed sebagai User.

Menggunakan interface untuk Action Param

Interface yang sama bisa dipakai di argumen action, sehingga tidak ada duplikasi:

JSInterface dipakai di action
actions: {
  addUser(user: User) {
    this.list.push(user)
  },
}

addUser(user: User) memakai interface User yang sama dengan state. Kalau struktur data berubah, TypeScript akan menunjuk semua tempat yang perlu diperbarui.

Typing Plugin dengan PiniaCustomProperties

Properti yang ditambahkan plugin tidak dikenal TypeScript secara otomatis. Perluas modul Pinia lewat module augmentation:

JSTyping properti plugin
declare module 'pinia' {
  export interface PiniaCustomProperties<Id, S, G, A> {
    $logger: (message: string) => void
  }
}

Dengan deklarasi di atas, setiap store akan memiliki $logger yang ter-typed:

JSPlugin memakai properti yang ter-typed
pinia.use(({ store }) => {
  store.$logger = (message) => console.log(`[${store.$id}]`, message)
})

PiniaCustomProperties<Id, S, G, A> memperluas tipe instance store sehingga properti tambahan plugin ikut dikenali — IDE memberi autocomplete dan error saat salah memakai.

Setup Store: Return Type Inference

Setup store menginferensikan tipe dari nilai yang dikembalikan — tidak perlu anotasi tambahan:

JSSetup store dengan inference
export const useCartStore = defineStore('cart', () => {
  const items = ref<{ name: string; price: number }[]>([])
 
  function addItem(item: { name: string; price: number }) {
    items.value.push(item)
  }
 
  return { items, addItem }
})
 
store.addItem({ name: 'Kopi', price: 15000 })
store.items // Ref<{ name: string; price: number }[]>

defineStore('cart', () => ...) menyimpulkan bahwa items adalah Ref dan addItem adalah fungsi.

Akses Store Lain yang Ter-typed

Mengakses store lain dari store lain juga ikut ter-typed otomatis:

JSCross-store typing
const userStore = useUserStore()
userStore.name // string, dikenal compiler
userStore.setName('Arman') // tipe argumen divalidasi

useUserStore() mengembalikan instance store yang sudah memiliki tipe lengkap. Menyerahkan argumen dengan tipe salah akan langsung ditolak compiler sebelum masuk runtime.

Tip

Untuk tipe this di action yang diperluas plugin, gunakan PiniaCustomStateProperties — analog dengan PiniaCustomProperties tetapi untuk state dan this.

Penutup

Episode 16 menunjukkan betapa dalamnya integrasi Pinia dengan TypeScript. Kalian sekarang bisa menikmati inference otomatis, memakai interface untuk state kompleks, mengetik properti plugin dengan PiniaCustomProperties, dan memahami return type inference pada Setup store.

Inti yang harus dibawa pulang:

  • Type store diinferensikan otomatis, termasuk this di action.
  • Interface membantu untuk state dengan struktur kompleks.
  • PiniaCustomProperties mengetik properti tambahan dari plugin.
  • Setup store menginferensikan tipe dari nilai yang dikembalikan.
  • Akses store lain selalu fully typed.
  • Module augmentation membuat plugin terasa native di IDE.

Di episode 17 selanjutnya kita akan membahas testing dengan @pinia/testing — createTestingPinia untuk membuat pinia tiruan, reset state antar test, mocking action dengan vi.fn, serta component testing dengan Vue Test Utils dan Vitest.

Belajar Pinia - TypeScript Deep Dive | Belajar Pinia