Типизация stores (Pinia)

Pinia является официальным state management решением для Vue.js и обеспечивает удобный, реактивный и типобезопасный способ управления состоянием приложения. Одним из ключевых аспектов современного использования Pinia является типизация store, что особенно важно при работе с TypeScript. Типизация позволяет получить автодополнение, проверку типов во время компиляции и минимизировать ошибки, связанные с некорректным использованием состояния или действий.


Определение store с типами

Для создания типизированного store в Pinia используется функция defineStore. Основной подход заключается в том, чтобы явно описывать интерфейс состояния и типы геттеров и действий.

import { defineStore } from 'pinia'

interface UserState {
  id: number
  name: string
  isAdmin: boolean
}

export const useUserStore = defineStore('user', {
  state: (): UserState => ({
    id: 0,
    name: '',
    isAdmin: false,
  }),
  getters: {
    welcomeMessage: (state): string => `Добро пожаловать, ${state.name}!`
  },
  actions: {
    setName(newName: string) {
      this.name = newName
    },
    promoteToAdmin() {
      this.isAdmin = true
    }
  }
})

Ключевые моменты:

  • state возвращает объект с типом UserState. Тип указывается явно через (): UserState.
  • Геттеры имеют типизацию как для входящего состояния (state), так и для возвращаемого значения.
  • Методы actions используют типы аргументов и позволяют безопасно изменять состояние.

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

При подключении store к компоненту Vue типизация сохраняется и предоставляет полное автодополнение.

<script lang="ts" setup>
import { useUserStore } from '@/stores/user'

const userStore = useUserStore()

userStore.setName('Иван')
console.log(userStore.welcomeMessage)
</script>

Преимущества:

  • Автодополнение для методов actions и геттеров.
  • Ошибки типов фиксируются на этапе компиляции, предотвращая runtime-ошибки.
  • Возможность использования реактивных ссылок с ref и computed без потери типизации.

Типизация геттеров с computed

Для более сложных геттеров можно использовать возвращаемые типы через ReturnType или явное указание типа:

getters: {
  fullName(state): string {
    return `${state.name} (${state.isAdmin ? 'Админ' : 'Пользователь'})`
  }
}

Если геттер зависит от других геттеров:

getters: {
  userStatus(): string {
    return this.isAdmin ? 'Администратор' : 'Обычный пользователь'
  }
}

Использование this в геттерах возможно только при корректной типизации store через defineStore. Pinia автоматически связывает типы state и actions с контекстом this.


Типизация actions с асинхронными операциями

Actions часто используются для выполнения асинхронных операций. Типизация помогает корректно определить возвращаемый тип промиса:

actions: {
  async fetchUserData(userId: number): Promise<void> {
    const response = await fetch(`/api/users/${userId}`)
    const data = await response.json()
    this.id = data.id
    this.name = data.name
    this.isAdmin = data.isAdmin
  }
}

Особенности:

  • Аргументы actions типизированы (userId: number).
  • Тип возвращаемого значения указывается через Promise<void> или конкретный тип данных.
  • Реактивность состояния сохраняется автоматически.

Типизация store с Composition API

Pinia также поддерживает подход через Composition API, когда store создаётся как набор функций и реактивных переменных:

import { ref, computed } from 'vue'
import { defineStore } from 'pinia'

export const useCartStore = defineStore('cart', () => {
  const items = ref<{ id: number; name: string; quantity: number }[]>([])

  const totalItems = computed(() =>
    items.value.reduce((sum, item) => sum + item.quantity, 0)
  )

  function addItem(item: { id: number; name: string; quantity: number }) {
    items.value.push(item)
  }

  return { items, totalItems, addItem }
})

Преимущества:

  • Использование реактивных ref и computed с полной типизацией.
  • Возможность создавать store без интерфейсов, используя непосредственно типы переменных.
  • Прямой доступ к методам и вычисляемым свойствам с сохранением типизации.

Вывод типов store

Для передачи типизированного store между модулями и компонентами удобно использовать ReturnType:

type UserStoreType = ReturnType<typeof useUserStore>

const store: UserStoreType = useUserStore()

Это позволяет гарантировать корректность типов при передаче store как пропса или контекста.


Рекомендации по типизации

  • Всегда определять интерфейс состояния (state) и использовать его при объявлении store.
  • Явно типизировать аргументы и возвращаемые значения actions и геттеров.
  • При использовании Composition API указывать типы для ref и computed.
  • Для сложных приложений создавать отдельные типы store для облегчения автодополнения и поддержки крупных проектов.

Типизация в Pinia повышает надежность кода и позволяет использовать все преимущества TypeScript в сочетании с реактивностью Vue.js, делая управление состоянием безопасным и предсказуемым.