логотип PurpleSchool
логотип PurpleSchool

Интеграция Pinia для управления состоянием в Nuxt

Автор

Олег Марков

Введение

При создании современных приложений на Vue часто возникает потребность в удобной, масштабируемой и простой в поддержке системе управления состоянием. На смену классическому Vuex сейчас приходит Pinia — легковесная и мощная библиотека, официально признанная основным state manager для Vue 3. В этом материале разберём процесс интеграции Pinia в проекты на Nuxt (особенно актуально для Nuxt 3), изучим основные принципы его работы, сравним с Vuex и посмотрим практические примеры настройки и использования хранилищ.

Будет полезно как для новичков, только осваивающих архитектуру state management, так и для опытных разработчиков, ищущих оптимальные подходы к построению архитектуры на новом стеке. Здесь вы найдёте готовые фрагменты кода, полезные советы и пояснения к основным понятиям.

Зачем нужен Pinia и почему он стал стандартом для Nuxt

Pinia — это современный инструмент для управления состоянием во Vue-приложениях. Если вы работали с Vuex, то заметите, что Pinia предлагает более простой API, поддержку Composition API "из коробки" и такие удобства, как поддержка типов для TypeScript без дополнительного кода.

Pinia был выбран официальным state-менеджером для Vue 3 по нескольким причинам:

  • Простота интеграции.
  • Меньше бойлерплейта (шаблонного кода).
  • Расширенные опции для работы с асинхронностью.
  • Безопасная и естественная работа с SSR (Server-Side Rendering), что особенно важно для Nuxt.
  • Совместимость с tooling экосистемы Vue.

На фоне этих плюсов становится понятно, почему его рекомендуют использовать во всех новых Nuxt-проектах.

Как устроена интеграция Pinia в Nuxt

Для Nuxt 3 интеграция Pinia практически бесшовная — официальная документация сама рекомендует его как основной state manager. Если вы используете более старые версии Nuxt (2.x), подключение делается вручную через отдельный пакет и плагин. В этой статье мы будем разбирать именно подход Nuxt 3.

Интеграция Pinia предоставляет современный и удобный способ управления состоянием в Nuxt-приложениях, заменяя Vuex. Но просто подключить Pinia недостаточно, важно понимать, как правильно организовать структуру хранилища, как использовать actions и getters, и как интегрировать Pinia с другими частями вашего приложения. Если вы хотите освоить все тонкости интеграции Pinia в Nuxt, приходите на наш большой курс Nuxt - fullstack Vue фреймворк. На курсе 129 уроков и 13 упражнений, AI-тренажеры для безлимитной практики с кодом и задачами 24/7, решение задач с живым ревью наставника, еженедельные встречи с менторами.

Установка Pinia в проект Nuxt

Если вы только начинаете проект на Nuxt 3, можно добавить Pinia сразу при создании через опцию при запуске npx nuxi init. Но во многих случаях потребуется интегрировать Pinia в уже существующий проект.

Вот как это делается:

# Устанавливаем @pinia/nuxt — специальный модуль для интеграции
npm install @pinia/nuxt
# или
yarn add @pinia/nuxt

После установки переходим к регистрации модуля в файле nuxt.config.ts:

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

На этом базовое внедрение завершено — Pinia автоматически зарегистрируется как плагин во всё Nuxt-приложение.

Что происходит "под капотом"

После описанных выше шагов:

  • При каждом запуске приложения создаётся собственный экземпляр Pinia-хранилища.
  • Pinia становится доступен во всех компонентах, страницах, плагинах Nuxt.
  • Все стейты (stores), созданные через Pinia, становятся универсальными — они корректно работают и на клиенте, и на сервере (SSR).

Теперь давайте разберёмся, как на практике создавать, использовать и организовывать хранилища.

Основные функции и возможности Pinia в Nuxt

Создание первого Pinia-стора

Pinia основывается на понятии "store" — это объект, который инкапсулирует некоторый state, геттеры и экшены.

В Nuxt принято размещать сторы в папке stores. Допустим, вы хотите сделать глобальное хранилище для пользователя. Давайте создадим пример:

// stores/user.js
import { defineStore } from 'pinia'

// Здесь мы определяем store с именем 'user'
export const useUserStore = defineStore('user', {
  // state — это функция, возвращающая объект состояния
  state: () => ({
    name: '',
    loggedIn: false,
  }),
  // getters — вычисляемые свойства на основе state
  getters: {
    // Вычисляет длину имени пользователя
    nameLength: (state) => state.name.length,
  },
  // actions — методы для изменения состояния и выполнения логики
  actions: {
    login(name) {
      this.name = name        // Изменяем состояние
      this.loggedIn = true    // Меняем статус входа
    },
    logout() {
      this.name = ''
      this.loggedIn = false
    }
  }
})

Пояснения по коду:

  • defineStore — функция для создания стора.
  • Первый аргумент ('user') — уникальное имя стора.
  • state — начальное состояние, как функция для предотвращения утечек состояния при SSR.
  • getters — вычисляемые свойства, которые кэшируются и автоматически обновляются.
  • actions — любые методы, которые изменяют state или содержат побочные эффекты.

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

Теперь, когда у нас есть store, давайте посмотрим, как использовать его в компонентах.

В компоненте Nuxt (или обычном Vue-файле) пишем так:

<script setup>
import { useUserStore } from '@/stores/user'

const userStore = useUserStore()

// Пример использования
function loginUser() {
  userStore.login('Alex')    // Вызов action
}
</script>

<template>
  <div>
    <p>Имя пользователя: {{ userStore.name }}</p>
    <p>Длина имени: {{ userStore.nameLength }}</p>
    <button @click="loginUser">Войти</button>
    <button @click="userStore.logout">Выйти</button>
  </div>
</template>

Обратите внимание:

  • В Pinia всё реактивно "из коробки" — повторные вызовы не нужны.
  • Если использовать Composition API (через <script setup>), обращаться к store очень просто.
  • Все state, getters и actions доступны как обычные свойства.

Автоматическая типизация Pinia-сторов

Pinia полностью совместим с TypeScript. Если вы используете .ts-файлы или TypeScript-поддержку в проекте Nuxt, сторы автоматически получают типы.

Пример:

// stores/counter.ts
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0
  }),
  actions: {
    increment() {
      this.count++
    }
  }
})

Теперь при обращении к useCounterStore().count вы получите числовой тип. Всё автокомплитится, что очень удобно для отладки.

Взаимодействие между разными сторами

Можно комбинировать сторы и обращаться к одному store из другого. Вот пример:

// stores/notifications.js
import { defineStore } from 'pinia'

export const useNotificationStore = defineStore('notifications', {
  state: () => ({ messages: [] }),
  actions: {
    add(message) {
      this.messages.push(message)
    }
  }
})

// stores/tasks.js
import { defineStore } from 'pinia'
import { useNotificationStore } from './notifications'

export const useTasksStore = defineStore('tasks', {
  state: () => ({ tasks: [] }),
  actions: {
    addTask(task) {
      this.tasks.push(task)
      // Получаем доступ к другому store
      const notifications = useNotificationStore()
      notifications.add(`Задача добавлена: ${task.title}`)
    }
  }
})

Это очень удобно для организации логики уведомлений, авторизации, кастомной работы с ошибками и других кросс-модулей.

Использование Pinia с SSR и Nuxt middleware

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

Глобальный доступ к сторам

В коде Nuxt вы можете использовать свой стор прямо в middleware, хуках и других местах, вот пример использования store в middleware:

// middleware/auth.js
import { useUserStore } from '@/stores/user'

export default defineNuxtRouteMiddleware((to, from) => {
  const userStore = useUserStore()
  if (!userStore.loggedIn && to.name !== 'login') {
    // Перенаправляем на страницу логина
    return navigateTo('/login')
  }
})

Такой подход позволяет централизовать логику авторизации.

Асинхронные actions и запросы к API

В Pinia actions могут быть асинхронными просто через async. Давайте рассмотрим пример:

// stores/posts.js
import { defineStore } from 'pinia'
export const usePostsStore = defineStore('posts', {
  state: () => ({
    posts: [],
    loading: false,
    error: null,
  }),
  actions: {
    async fetchPosts() {
      this.loading = true
      this.error = null
      try {
        // Здесь используем $fetch — фичу Nuxt для запросов к API
        this.posts = await $fetch('/api/posts')
      } catch (e) {
        this.error = e.message
      } finally {
        this.loading = false
      }
    }
  }
})

В компонентах можно вызывать такие actions точно так же, как синхронные:

<script setup>
import { usePostsStore } from '@/stores/posts'
const postsStore = usePostsStore()

onMounted(() => {
  postsStore.fetchPosts()  // Автоматически загружаем список постов при монтировании компонента
})
</script>

Реактивность и использование storeToRefs

Pinia предоставляет функцию storeToRefs, чтобы корректно извлекать реактивные свойства state и getters без потери реактивности. Это может быть полезно для деструктуризации:

import { storeToRefs } from 'pinia'
import { useUserStore } from '@/stores/user'

const userStore = useUserStore()
// Получаем только нужные реактивные свойства
const { name, loggedIn } = storeToRefs(userStore)

Модули и организация стора

В отличие от Vuex, Pinia не навязывает иерархических модулей. Каждый store — независимая сущность. Лучше создавать сторы по сущностям или доменам приложения (например, user, posts, notifications).

Для крупных проектов удобно организовывать сторы по подпапкам внутри stores. Нет необходимости вручную регистрировать каждый store — Pinia всё найдёт автоматически.

SSR, hydratation и синхронизация состояния между сервером и клиентом

Pinia совместно с Nuxt заботится о детальной синхронизации:

  • State генерируется на сервере и отправляется вместе с HTML.
  • На клиенте состояние автоматически гидратируется (синхронизируется).
  • Происходит правильная изоляция состояний между пользователями на сервере.

Для большинства задач не нужно заботиться о гидратации вручную, однако в некоторых случаях (если нужны сложные мутации сторы при запуске) стоит учитывать сервер-клиентное разделение.

Заключение

Интеграция Pinia с Nuxt — это быстрый и надёжный способ реализации стейт-менеджмента в современных Vue-приложениях. Благодаря простому API, поддержке SSR и тесной интеграции с экосистемой Nuxt, Pinia становится оптимальным выбором для большинства задач — от простых до сложных систем. Здесь мы рассмотрели процесс установки, базовые и продвинутые особенности, а также подходы к организации кода. Внедряя Pinia, вы получаете мощный инструмент с хорошей документацией и поддержкой, который ускоряет разработку и делает поддержку состояния приложения более удобной.

Интеграция Pinia - это отличный способ улучшить управление состоянием в ваших Nuxt-приложениях. Чтобы создавать действительно мощные и масштабируемые решения, необходимо освоить все аспекты фреймворка, включая работу с сервером, базами данных и API. На нашем курсе Nuxt - fullstack Vue фреймворк вы найдете все необходимые знания и навыки для достижения успеха. В первых 3 модулях уже доступно бесплатное содержание — начните погружаться в Nuxt прямо сегодня.

Частозадаваемые технические вопросы по теме

1. Как использовать Pinia-хранилище в плагинах Nuxt?
В плагине можно импортировать определённый store напрямую и использовать как обычно. Пример для plugins/myPlugin.ts:
ts import { useUserStore } from '@/stores/user' export default defineNuxtPlugin(() => { const userStore = useUserStore() // Используйте userStore здесь для операций })

2. Как сбросить состояние store Pinia на значения по умолчанию?
Внутри каждого стора добавьте action для сброса: js reset() { this.$reset() // встроенный метод сброса до изначального состояния }

3. Как подписываться на изменения состояния (watch state)?
Можно использовать функцию watch из Vue: js import { watch } from 'vue' watch(() => userStore.loggedIn, (newVal) => { // Реагируем на изменение loggedIn })

4. Как подключить сторонние плагины или расширения к Pinia?
Создайте отдельный плагин в папке plugins, используйте функцию pinia.use: js import { defineNuxtPlugin } from '#app' export default defineNuxtPlugin((nuxtApp) => { nuxtApp.$pinia.use((context) => { // Ваш код плагина Pinia }) })

5. Как использовать динамические store (например, store для каждой вкладки окна браузера)?
Pinia поддерживает создание раздельных store-инстансов через функцию defineStore(id, options). Используйте уникальный id для каждой вкладки или типа данных, чтобы стейт не пересекался.

Как использовать шаблоны Templates в NuxtСтрелочка вправо

Постройте личный план изучения Nuxt до уровня Middle — бесплатно!

Nuxt — часть карты развития Frontend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Бесплатные лекции

Все гайды по Nuxt

Генерация статического сайта с помощью NuxtКак использовать useAsyncData в NuxtКак использовать SVG в NuxtКоманды для запуска NuxtРабота с JSON-данными в NuxtКак настроить HTML-шаблон в NuxtГенерация статического сайта с помощью NuxtРуководство по получению данных с Fetch в NuxtКак отключить определенные функции в NuxtРабота с данными в Nuxt с помощью useNuxtDataНастройка и использование cookie в NuxtГайд по аутентификации (auth) в NuxtКак создавать API-маршруты в Nuxt
Использование Vite для ускорения разработки в NuxtРуководство по использованию TypeScript в NuxtКак запустить Nuxt-приложение в productionКак работает Server-Side Rendering SSR в NuxtНастройка и оптимизация серверной части Nuxt-приложенияРуководство по настройке маршрутизации в NuxtКакие проекты разумно реализовывать на NuxtИнструкция по управлению пакетами NPM в NuxtИнтеграция Node.js для Nuxt-приложенияНастройка мета-тегов для SEO в NuxtНастройка и использование HTTPS в NuxtКак отлавливать и обрабатывать ошибки в NuxtКак развернуть Nuxt приложение в DockerРуководство по развертыванию приложений в Nuxt CloudКак оптимизировать сборку на NuxtИнтеграция Laravel и Nuxt
Открыть базу знаний

Лучшие курсы по теме

Иконка ракетыСкоро!
изображение курса

Nuxt

Антон Ларичев
изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий