Pinia

Pinia является официальным решением для управления состоянием в приложениях на Vue 3, заменяя Vuex и предлагая более простую и интуитивную архитектуру. В основе Pinia лежит концепция stores — централизованных хранилищ состояния, которые могут использоваться в любом компоненте приложения.


Создание хранилища (Store)

Хранилище в Pinia создается с помощью функции defineStore. Основные элементы хранилища:

  • id — уникальный идентификатор хранилища.
  • state — реактивное состояние, возвращаемое функцией.
  • getters — вычисляемые свойства на основе состояния.
  • actions — методы, которые могут изменять состояние или выполнять асинхронные операции.

Пример простого хранилища:

import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0
  }),
  getters: {
    doubleCount: (state) => state.count * 2
  },
  actions: {
    increment() {
      this.count++
    },
    incrementBy(amount) {
      this.count += amount
    }
  }
})

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

  • state всегда является функцией, возвращающей объект, что обеспечивает независимость экземпляров хранилища.
  • getters автоматически получают реактивность и кеширование.
  • actions имеют доступ к this внутри хранилища, что позволяет изменять состояние напрямую.

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

Хранилище подключается через функцию useStore в любом компоненте:

<script setup>
import { useCounterStore } from '@/stores/counter'

const counterStore = useCounterStore()

function incrementCounter() {
  counterStore.increment()
}
</script>

<template>
  <div>
    <p>Счёт: {{ counterStore.count }}</p>
    <p>Удвоенный счёт: {{ counterStore.doubleCount }}</p>
    <button @click="incrementCounter">Увеличить</button>
  </div>
</template>

Важно: изменения состояния хранилища автоматически отражаются во всех компонентах, использующих это хранилище.


Асинхронные действия

Pinia поддерживает асинхронные операции в actions без необходимости использовать внешние middleware:

actions: {
  async fetchData() {
    const response = await fetch('https://api.example.com/data')
    this.data = await response.json()
  }
}

Асинхронные методы могут напрямую изменять реактивное состояние, что упрощает работу с API и внутренней логикой приложения.


Модули и разделение хранилищ

Pinia не ограничивает одним глобальным хранилищем. Каждый store имеет свою область ответственности:

// stores/user.js
export const useUserStore = defineStore('user', {
  state: () => ({
    name: '',
    email: ''
  }),
  actions: {
    setUser(data) {
      this.name = data.name
      this.email = data.email
    }
  }
})

Такой подход обеспечивает модульность, улучшает читаемость и тестируемость кода.


Подключение Pinia к приложению Vue

Для интеграции Pinia с Vue 3 используется основной экземпляр приложения:

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const app = createApp(App)
const pinia = createPinia()

app.use(pinia)
app.mount('#app')

После этого любое хранилище можно подключать в компонентах через useStore.


Хранение состояния между перезагрузками

Pinia поддерживает плагины для сохранения состояния, например, в localStorage. Использование плагина позволяет автоматически сохранять и восстанавливать состояние:

import { createPinia } from 'pinia'
import piniaPersist from 'pinia-plugin-persistedstate'

const pinia = createPinia()
pinia.use(piniaPersist)

В хранилище нужно добавить:

export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0 }),
  persist: true
})

Состояние будет автоматически сохраняться и восстанавливаться при перезагрузке страницы.


Типизация и TypeScript

Pinia полностью поддерживает TypeScript. Определение хранилища с типами:

interface CounterState {
  count: number
}

export const useCounterStore = defineStore<'counter', CounterState>({
  id: 'counter',
  state: () => ({ count: 0 }),
  getters: {
    doubleCount: (state) => state.count * 2
  },
  actions: {
    increment() {
      this.count++
    }
  }
})

Типизация обеспечивает автодополнение, безопасные изменения состояния и ясную структуру хранилища.


Реактивность и отслеживание изменений

Состояние в Pinia полностью реактивно. Можно использовать его в watch или computed:

import { watch } from 'vue'
import { useCounterStore } from '@/stores/counter'

const counterStore = useCounterStore()

watch(() => counterStore.count, (newValue, oldValue) => {
  console.log(`Счёт изменился с ${oldValue} на ${newValue}`)
})

Любые изменения состояния автоматически триггерят реакции, что упрощает работу с UI и внутренней логикой.


Совместимость с Composition API и Options API

Pinia одинаково удобно использовать как с setup(), так и с классическим Options API:

export default {
  computed: {
    count() {
      return useCounterStore().count
    }
  },
  methods: {
    increment() {
      useCounterStore().increment()
    }
  }
}

Такое сочетание позволяет интегрировать Pinia даже в старые проекты на Vue 3.


Рекомендации по архитектуре

  • Каждое хранилище должно содержать логику только одной области приложения.
  • Использовать getters для производных значений, а actions для мутаций состояния и асинхронных операций.
  • Применять плагины для сохранения состояния или логирования при необходимости.
  • Разделять глобальное и локальное состояние для уменьшения зависимости компонентов.

Pinia обеспечивает современный, легкий и мощный подход к управлению состоянием, значительно упрощая архитектуру Vue-приложений и делая код более предсказуемым и масштабируемым.