Архитектура

Pinia для state management

Контекст

Нужно решение для управления глобальным состоянием приложения.

Решение

Использовать Pinia как единственное решение для state management.

Обоснование

КритерийPinia
Vue 3Нативная поддержка
TypeScriptFirst-class
DevToolsОтличная интеграция
Composition APIНативная
Bundle sizeМаленький
NuxtМодуль @pinia/nuxt

Настройка

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@pinia/nuxt'],
})

Паттерн store (Setup Store)

// app/stores/user.ts
export const useUserStore = defineStore('user', () => {
  // State
  const user = ref<User | null>(null)
  const token = ref<string | null>(null)

  // Getters
  const isAuthenticated = computed(() => !!token.value)
  const fullName = computed(() =>
    user.value ? `${user.value.firstName} ${user.value.lastName}` : ''
  )

  // Actions
  async function login(credentials: LoginCredentials) {
    const config = useRuntimeConfig()
    // Login — публичный endpoint, используем $fetch
    const response = await $fetch<LoginResponse>('/auth/login', {
      baseURL: config.public.apiBase,
      method: 'POST',
      body: credentials,
    })
    token.value = response.token
    user.value = response.user
    localStorage.setItem('token', response.token)
  }

  function logout() {
    token.value = null
    user.value = null
    localStorage.removeItem('token')
    navigateTo('/login')
  }

  // Init (SPA — читаем из localStorage при старте)
  function init() {
    const savedToken = localStorage.getItem('token')
    if (savedToken) {
      token.value = savedToken
      // fetchCurrentUser()
    }
  }

  return {
    user,
    token,
    isAuthenticated,
    fullName,
    login,
    logout,
    init,
  }
})

Использование

<script setup lang="ts">
import { storeToRefs } from 'pinia'

const userStore = useUserStore()
const { user, isAuthenticated } = storeToRefs(userStore)

function handleLogout() {
  userStore.logout()
}
</script>

Правила

1. Используй storeToRefs для реактивности

// ❌ Потеря реактивности
const { user } = userStore

// ✅ Сохранение реактивности
const { user } = storeToRefs(userStore)

2. Не мутируй state напрямую

// ❌ Плохо
userStore.user.name = 'New Name'

// ✅ Хорошо
userStore.updateProfile({ name: 'New Name' })

3. Init в plugin (SPA)

// app/plugins/init.client.ts
export default defineNuxtPlugin(() => {
  const userStore = useUserStore()
  userStore.init()
})

Последствия

Положительные

  • Простой API
  • Отличная TypeScript поддержка
  • Auto-imports в Nuxt

Отрицательные

  • Нужна дисциплина при мутациях

Связанные решения