1. Pinia設定
// main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const app = createApp(App)
app.use(createPinia())
app.mount('#app')
2. 商店的定義
// stores/useAuthStore.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
// Setup syntax (recommended)
export const useAuthStore = defineStore('auth', () => {
// State
const user = ref<User | null>(null)
const token = ref<string | null>(null)
const isLoading = ref(false)
// Getters
const isAuthenticated = computed(() => !!token.value)
const isAdmin = computed(() => user.value?.role === 'admin')
// Actions
async function login(email: string, password: string) {
isLoading.value = true
try {
const res = await fetch('/api/auth/login', {
method: 'POST',
body: JSON.stringify({ email, password }),
})
const data = await res.json()
user.value = data.user
token.value = data.token
} finally {
isLoading.value = false
}
}
function logout() {
user.value = null
token.value = null
}
return { user, token, isLoading, isAuthenticated, isAdmin, login, logout }
})
3. 使用商店
<script setup lang="ts">
import { useAuthStore } from '@/stores/useAuthStore'
import { storeToRefs } from 'pinia'
const authStore = useAuthStore()
// ⚠️ Cần storeToRefs để giữ reactivity khi destructure
const { user, isAuthenticated, isLoading } = storeToRefs(authStore)
// Actions có thể destructure trực tiếp
const { login, logout } = authStore
</script>
<template>
<div v-if="isAuthenticated">
<p>Xin chào, {{ user?.name }}</p>
<button @click="logout">Đăng xuất</button>
</div>
<div v-else>
<button @click="login('[email protected]', '123')" :disabled="isLoading">
{{ isLoading ? 'Đang đăng nhập...' : 'Đăng nhập' }}
</button>
</div>
</template>
4. 店鋪構成
// stores/useCartStore.ts
export const useCartStore = defineStore('cart', () => {
const authStore = useAuthStore() // Sử dụng store khác
const items = ref<CartItem[]>([])
const total = computed(() =>
items.value.reduce((sum, item) => sum + item.price * item.qty, 0)
)
async function checkout() {
if (!authStore.isAuthenticated) {
throw new Error('Please login first')
}
await fetch('/api/checkout', {
method: 'POST',
headers: { Authorization: `Bearer ${authStore.token}` },
body: JSON.stringify({ items: items.value }),
})
items.value = []
}
return { items, total, checkout }
})
5. Pinia 插件
// plugins/piniaPersistedState.ts
import { watch } from 'vue'
import type { PiniaPluginContext } from 'pinia'
export function persistedState({ store }: PiniaPluginContext) {
const key = `pinia-${store.$id}`
// Restore state from localStorage
const saved = localStorage.getItem(key)
if (saved) {
store.$patch(JSON.parse(saved))
}
// Persist on change
watch(
() => store.$state,
(state) => {
localStorage.setItem(key, JSON.stringify(state))
},
{ deep: true }
)
}
// main.ts
const pinia = createPinia()
pinia.use(persistedState)
6. 選項語法(替代)
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
}),
getters: {
doubled: (state) => state.count * 2,
},
actions: {
increment() {
this.count++
},
},
})
下一篇: 可組合和可重複使用邏輯 — 自訂可組合項,VueUse。