Nuxt 3 狀態管理方案全面比較
Nuxt 3 狀態管理方案全面比較
Nuxt 3 為開發者提供了多種狀態管理方案,從輕量的 useState 到完整的 Pinia,選擇適合的工具對專案架構至關重要。這篇文章比較各方案的適用情境,並提供完整的實作範例。
方案一:Nuxt 內建 useState
useState 是 Nuxt 3 提供的 SSR 友好的響應式狀態。它的特點是簡單、不需要額外安裝,最適合跨元件共享的簡單狀態。
// composables/useCounter.ts
export const useCounter = () => {
const count = useState<number>('counter', () => 0)
const increment = () => count.value++
const decrement = () => count.value--
const reset = () => count.value = 0
return { count: readonly(count), increment, decrement, reset }
}<!-- pages/index.vue -->
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="increment">+</button>
<button @click="decrement">-</button>
</div>
</template>
<script setup>
const { count, increment, decrement } = useCounter()
</script>useState 的 SSR 特性
// 在 server 端設定的值,會自動傳遞到 client 端
// key 相同的 useState 在整個應用中共享同一狀態
const user = useState<User | null>('current-user', () => null)
// 在 server-side 取得用戶資料
const { data } = await useFetch('/api/me')
user.value = data.value
// 頁面 hydration 後,client 端自動擁有相同的狀態方案二:Pinia(推薦用於中大型專案)
npm install pinia @pinia/nuxt// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@pinia/nuxt']
})定義 Store
// stores/auth.ts
import { defineStore } from 'pinia'
interface User {
id: string
name: string
email: string
role: 'user' | 'admin'
}
export const useAuthStore = defineStore('auth', () => {
const user = ref<User | null>(null)
const token = ref<string | null>(null)
const isLoggedIn = computed(() => !!user.value)
const isAdmin = computed(() => user.value?.role === 'admin')
async function login(email: string, password: string) {
const { data, error } = await useFetch('/api/auth/login', {
method: 'POST',
body: { email, password }
})
if (error.value) throw error.value
user.value = data.value!.user
token.value = data.value!.token
}
function logout() {
user.value = null
token.value = null
navigateTo('/login')
}
return { user, token, isLoggedIn, isAdmin, login, logout }
})Pinia 持久化
npm install pinia-plugin-persistedstate @pinia-plugin-persistedstate/nuxt// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@pinia/nuxt', '@pinia-plugin-persistedstate/nuxt']
})
// stores/settings.ts
export const useSettingsStore = defineStore('settings', () => {
const theme = ref<'light' | 'dark'>('light')
const language = ref<'zh-TW' | 'en'>('zh-TW')
return { theme, language }
}, {
persist: {
storage: persistedState.localStorage,
paths: ['theme', 'language']
}
})方案三:provide / inject(元件樹狀態共享)
適合在特定元件子樹中共享狀態,不污染全域:
// composables/useFormContext.ts
const FORM_KEY = Symbol('form-context')
interface FormContext {
values: Ref<Record<string, any>>
errors: Ref<Record<string, string>>
setError: (field: string, message: string) => void
clearError: (field: string) => void
}
export function provideFormContext(initialValues: Record<string, any>) {
const values = ref(initialValues)
const errors = ref<Record<string, string>>({})
const setError = (field: string, message: string) => {
errors.value[field] = message
}
const clearError = (field: string) => {
delete errors.value[field]
}
const context: FormContext = { values, errors, setError, clearError }
provide(FORM_KEY, context)
return context
}
export function useFormContext(): FormContext {
const context = inject<FormContext>(FORM_KEY)
if (!context) throw new Error('必須在 Form 元件內使用')
return context
}方案四:Nuxt Payload(伺服器資料傳遞)
// 在 server plugin 設定
// server/plugins/data.ts
export default defineNitroPlugin(async (nitroApp) => {
nitroApp.hooks.hook('render:html', (html, { event }) => {
// 在 HTML 渲染前注入資料
})
})
// 在頁面中使用
const { data } = await useAsyncData('config', async () => {
const payload = useNuxtData('config')
if (payload.data.value) return payload.data.value
return await $fetch('/api/config')
})選擇建議
| 情境 | 推薦方案 |
|---|---|
| 簡單的跨頁面狀態 | `useState` |
| 複雜業務邏輯、多 store | Pinia |
| 需要持久化 | Pinia + persistedstate |
| 表單、元件樹範圍 | provide/inject |
| SSR 資料傳遞 | `useAsyncData` + `useNuxtData` |
Nuxt 3 的好處是這些方案可以混搭使用,根據不同情境選擇最合適的工具。小型專案用 useState 就夠了,隨著複雜度增加再引入 Pinia。
分享這篇文章