Интеграция Pinia в Quasar проект

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

Установка и настройка Pinia

Для начала необходимо установить Pinia через npm или yarn:

npm install pinia
# или
yarn add pinia

После установки Pinia интегрируется в Quasar через основной файл приложения, обычно src/main.js или src/boot/pinia.js. Создание отдельного boot-файла позволяет воспользоваться преимуществами Quasar Boot-процессов.

Пример подключения через boot-файл:

// src/boot/pinia.js
import { boot } from 'quasar/wrappers'
import { createPinia } from 'pinia'

export default boot(({ app }) => {
  const pinia = createPinia()
  app.use(pinia)
})

Необходимо убедиться, что boot-файл зарегистрирован в quasar.conf.js:

boot: [
  'pinia',
]

Это гарантирует, что Pinia будет доступна во всём приложении.

Создание и использование хранилищ

Хранилища Pinia (stores) представляют собой объекты, где хранится состояние, геттеры и методы для изменения данных.

Пример базового хранилища:

// src/stores/counter.js
import { defineStore } from 'pinia'

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

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

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

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

<template>
  <q-page>
    <q-btn @click="counter.increment" label="Increment" />
    <q-btn @click="counter.decrement" label="Decrement" />
    <div>Count: {{ counter.count }}</div>
    <div>Double Count: {{ counter.doubleCount }}</div>
  </q-page>
</template>

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

const counter = useCounterStore()
</script>

Работа с модулями и разделение логики

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

src/stores/
  auth.js
  user.js
  products.js

Это позволяет держать логику состояния изолированной и легко тестируемой. Также можно использовать Composition API внутри actions для сложных асинхронных операций:

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

export const useUserStore = defineStore('user', {
  state: () => ({
    profile: null,
    loading: false
  }),
  actions: {
    async fetchProfile() {
      this.loading = true
      try {
        const response = await axios.get('/api/user/profile')
        this.profile = response.data
      } finally {
        this.loading = false
      }
    }
  }
})

Типизация и TypeScript

Quasar и Pinia полностью совместимы с TypeScript. Для типизации состояния создаются интерфейсы:

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

interface CounterState {
  count: number
}

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

Это обеспечивает автокомплит и строгую проверку типов в редакторах кода и во время компиляции.

Интеграция с Quasar Components

Pinia легко интегрируется с компонентами Quasar, такими как QDialog, QDrawer, QTable, где состояние компонентов может храниться в централизованном store. Например, управление открытием диалога через store:

// src/stores/ui.js
import { defineStore } from 'pinia'

export const useUiStore = defineStore('ui', {
  state: () => ({
    isDialogOpen: false
  }),
  actions: {
    openDialog() {
      this.isDialogOpen = true
    },
    closeDialog() {
      this.isDialogOpen = false
    }
  }
})
<template>
  <q-page>
    <q-btn label="Open Dialog" @click="ui.openDialog" />
    <q-dialog v-model="ui.isDialogOpen">
      <q-card>
        <q-card-section>
          Контент диалога
        </q-card-section>
        <q-card-actions>
          <q-btn flat label="Закрыть" @click="ui.closeDialog" />
        </q-card-actions>
      </q-card>
    </q-dialog>
  </q-page>
</template>

<script setup>
import { useUiStore } from 'src/stores/ui'

const ui = useUiStore()
</script>

Синхронизация состояния с локальным хранилищем

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

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

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

Хранилище с сохранением состояния:

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

Это обеспечивает автоматическую сериализацию и десериализацию состояния.

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

Pinia позволяет создавать асинхронные actions, что удобно для работы с внешними API и интеграции с Quasar QSpinner, QLinearProgress и другими компонентами для отображения загрузки:

// src/stores/products.js
import { defineStore } from 'pinia'
import axios from 'axios'

export const useProductsStore = defineStore('products', {
  state: () => ({
    items: [],
    loading: false
  }),
  actions: {
    async fetchProducts() {
      this.loading = true
      try {
        const response = await axios.get('/api/products')
        this.items = response.data
      } finally {
        this.loading = false
      }
    }
  }
})

Подключение к компонентам:

<q-page>
  <q-linear-progress v-if="products.loading" indeterminate color="primary" />
  <q-list>
    <q-item v-for="item in products.items" :key="item.id">
      <q-item-section>{{ item.name }}</q-item-section>
    </q-item>
  </q-list>
</q-page>

<script setup>
import { useProductsStore } from 'src/stores/products'

const products = useProductsStore()
products.fetchProducts()
</script>

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

  • Разделять хранилища по функциональным областям: auth, ui, products.
  • Использовать boot-файлы для глобальной регистрации Pinia.
  • Применять TypeScript для автокомплита и проверки типов.
  • Сохранять состояние между сессиями через плагины Pinia.
  • Использовать асинхронные actions для работы с API и сторонними сервисами.

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