Стандарты

Pinia Stores

Структура stores/

app/stores/
├── user.ts           # Авторизация, текущий пользователь
├── sidebar.ts        # Состояние сайдбара
├── notification.ts   # Уведомления
└── collection.ts     # Справочники

Паттерн 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) ===
  function init() {
    const savedToken = localStorage.getItem('token')
    if (savedToken) {
      token.value = savedToken
      fetchCurrentUser()
    }
  }

  async function fetchCurrentUser() {
    if (!token.value) return
    // Авторизованный запрос — используем useAuthFetch
    const authFetch = useAuthFetch()
    try {
      user.value = await authFetch<User>('/auth/me')
    } catch {
      logout()
    }
  }

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

Инициализация (SPA)

В SPA режиме используем client plugin для инициализации:

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

Использование в компонентах

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

// Store автоимпортируется
const userStore = useUserStore()

// storeToRefs для реактивности state/getters
const { user, isAuthenticated } = storeToRefs(userStore)

// Actions вызываются напрямую
function handleLogout() {
  userStore.logout()
}
</script>

<template>
  <div v-if="isAuthenticated">
    {{ user?.firstName }}
    <UButton @click="handleLogout">Выйти</UButton>
  </div>
</template>

Правила

1. Используй storeToRefs

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

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

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

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

// ✅ Хорошо — через action
userStore.updateProfile({ name: 'New Name' })

3. Один store — одна ответственность

✅ useUserStore     — пользователь и авторизация
✅ useSidebarStore  — состояние UI сайдбара
✅ useOrdersStore   — список заказов (если нужен глобально)

❌ useAppStore      — всё в одном (слишком большой)

Примеры stores

// app/stores/sidebar.ts
export const useSidebarStore = defineStore('sidebar', () => {
  const collapsed = ref(false)

  function toggle() {
    collapsed.value = !collapsed.value
  }

  return { collapsed, toggle }
})

Notifications

// app/stores/notification.ts
interface Notification {
  id: string
  type: 'success' | 'error' | 'info'
  message: string
}

export const useNotificationStore = defineStore('notification', () => {
  const notifications = ref<Notification[]>([])

  function add(notification: Omit<Notification, 'id'>) {
    const id = Date.now().toString()
    notifications.value.push({ ...notification, id })

    setTimeout(() => remove(id), 5000)
  }

  function remove(id: string) {
    const index = notifications.value.findIndex(n => n.id === id)
    if (index > -1) {
      notifications.value.splice(index, 1)
    }
  }

  return { notifications, add, remove }
})

Чек-лист

  • Setup Store синтаксис
  • storeToRefs для state/getters
  • Init через client plugin (SPA)
  • Один store — одна ответственность
  • Типизация state и actions

Связанные стандарты