跳至主要內容

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。

分享這篇文章