Pinia: State Management สำหรับ Vue 3
Vue Developer•18 ธันวาคม 2567•State 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