Gensics

Back to Home

Pinia: State Management สำหรับ Vue 3

Vue Developer18 ธันวาคม 2567State Management
VuePiniaState ManagementVuex

Pinia เป็น state management library อย่างเป็นทางการสำหรับ Vue 3 ที่ถูกออกแบบมาให้ใช้งานง่ายและมีประสิทธิภาพ

การติดตั้งและตั้งค่า

npm install pinia
// main.js import { createApp } from 'vue' import { createPinia } from 'pinia' import App from './App.vue' const app = createApp(App) const pinia = createPinia() app.use(pinia) app.mount('#app')

สร้าง Store

// stores/counter.js import { defineStore } from 'pinia' export const useCounterStore = defineStore('counter', { state: () => ({ count: 0, name: 'Eduardo' }), getters: { doubleCount: (state) => state.count * 2, doubleCountPlusOne() { return this.doubleCount + 1 } }, actions: { increment() { this.count++ }, async fetchUserData(userId) { try { const response = await api.getUser(userId) this.name = response.name } catch (error) { console.error('Failed to fetch user:', error) } } } })

การใช้งานใน Component

<template> <div> <h1>{{ store.name }}</h1> <p>Count: {{ store.count }}</p> <p>Double Count: {{ store.doubleCount }}</p> <button @click="store.increment">Increment</button> <button @click="increment">Increment (destructured)</button> <button @click="reset">Reset</button> </div> </template> <script setup> import { storeToRefs } from 'pinia' import { useCounterStore } from '@/stores/counter' const store = useCounterStore() // Destructure with reactivity const { count, name, doubleCount } = storeToRefs(store) const { increment, $reset: reset } = store </script>

Composition API Style

// stores/todos.js import { ref, computed } from 'vue' import { defineStore } from 'pinia' export const useTodosStore = defineStore('todos', () => { // State const todos = ref([]) const filter = ref('all') // Getters const finishedTodos = computed(() => todos.value.filter(todo => todo.isFinished) ) const unfinishedTodos = computed(() => todos.value.filter(todo => !todo.isFinished) ) const filteredTodos = computed(() => { if (filter.value === 'finished') { return finishedTodos.value } else if (filter.value === 'unfinished') { return unfinishedTodos.value } return todos.value }) // Actions function addTodo(text) { todos.value.push({ id: Date.now(), text, isFinished: false }) } function toggleTodo(id) { const todo = todos.value.find(todo => todo.id === id) if (todo) { todo.isFinished = !todo.isFinished } } function removeTodo(id) { const index = todos.value.findIndex(todo => todo.id === id) if (index > -1) { todos.value.splice(index, 1) } } return { todos, filter, finishedTodos, unfinishedTodos, filteredTodos, addTodo, toggleTodo, removeTodo } })

Store Composition

// stores/auth.js import { defineStore } from 'pinia' import { useLocalStorage } from '@vueuse/core' export const useAuthStore = defineStore('auth', { state: () => ({ user: null, token: useLocalStorage('auth-token', null) }), getters: { isAuthenticated: (state) => !!state.user && !!state.token }, actions: { async login(credentials) { try { const response = await api.login(credentials) this.user = response.user this.token = response.token return { success: true } } catch (error) { return { success: false, error: error.message } } }, logout() { this.user = null this.token = null } } }) // stores/profile.js import { defineStore } from 'pinia' import { useAuthStore } from './auth' export const useProfileStore = defineStore('profile', { state: () => ({ profile: null, loading: false }), actions: { async fetchProfile() { const authStore = useAuthStore() if (!authStore.isAuthenticated) { throw new Error('User not authenticated') } this.loading = true try { this.profile = await api.getProfile(authStore.user.id) } finally { this.loading = false } } } })

Plugins

// plugins/pinia-logger.js function piniaLogger({ store }) { store.$subscribe((mutation) => { console.log(`[${store.$id}]: ${mutation.type}`, mutation) }) } // main.js const pinia = createPinia() pinia.use(piniaLogger)

Testing

// tests/counter.spec.js import { setActivePinia, createPinia } from 'pinia' import { useCounterStore } from '@/stores/counter' describe('Counter Store', () => { beforeEach(() => { setActivePinia(createPinia()) }) it('increments counter', () => { const counter = useCounterStore() expect(counter.count).toBe(0) counter.increment() expect(counter.count).toBe(1) }) it('computes double count', () => { const counter = useCounterStore() counter.count = 5 expect(counter.doubleCount).toBe(10) }) })

ข้อดีของ Pinia

  • Type Safety - TypeScript support ดีเยิ่ยม
  • DevTools - Vue DevTools integration
  • HMR - Hot Module Replacement support
  • Plugins - Extensible architecture
  • Composition API - ใช้งานได้ทั้ง Options และ Composition API
  • SSR - Server-side rendering support

Migration จาก Vuex

  • ไม่มี mutations (ใช้ actions โดยตรง)
  • ไม่มี modules (ใช้ multiple stores)
  • Better TypeScript support
  • Smaller bundle size

สรุป

Pinia ให้ developer experience ที่ดีกว่า Vuex พร้อมกับ TypeScript support ที่ดีเยิ่ยม และเป็น official state management สำหรับ Vue 3