Skip to content
Перевод синхронизирован с документацией от , хэш коммита 59ec609.

Вступление

Pinia начиналась как эксперимент по перепроектированию того, как может выглядеть хранилище для Vue с Composition API примерно в ноябре 2019 года. С тех пор начальные принципы остались неизменными, но Pinia работает как для Vue 2, так и для Vue 3 и не требует использования composition API. API одинаков для обоих вариантов, за исключением установки и SSR, и эта документация ориентирована на Vue 3 с примечаниями для Vue 2, когда это необходимо, чтобы ее могли читать пользователи Vue 2 и Vue 3!

Почему вы должны использовать Pinia?

Pinia - это библиотека для создания хранилища для Vue, которая позволяет вам совместно использовать состояние между компонентами/страницами. Если вы знакомы с Composition API, вы, возможно, думаете, что уже можете совместно использовать глобальное состояние с помощью простого export const state = reactive({}). Это верно для одностраничных приложений (SPA), но при использовании рендеринга на стороне сервера (SSR) это может привести к уязвимостям безопасности. Но даже в небольших одностраничных приложениях вы получаете много возможностей от использования Pinia:

  • Утилиты для тестирования
  • Плагины: расширение возможностей Pinia с помощью плагинов
  • Полноценная поддержка TypeScript или автозаполнение для пользователей JS
  • Поддержка рендеринга на стороне сервера (SSR)
  • Поддержка Devtools
    • Хронология для отслеживания действий, мутаций
    • Хранилища отображаются в компонентах, в которых они используются
    • Легкая отладка с возможностью перемещения во времени
  • Горячая замена модулей (HMR)
    • Изменение вашего хранилища без перезагрузки страницы
    • Сохранение существующего состояния в процессе разработки

Если вы всё ещё сомневаетесь, ознакомьтесь с официальным курсом Mastering Pinia. В самом начале мы рассмотрим, как создать собственную функцию defineStore(), а затем перейдем к официальному Pinia API.

Vue Mastery Logo Получить шпаргалку по Pinia от Vue Mastery

Простой пример

Вот как выглядит использование Pinia в терминах его API (не забудьте посмотреть руководство по началу работы, чтобы получить полную инструкцию). Начнем с создания хранилища:

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

export const useCounterStore = defineStore('counter', {
  state: () => {
    return { count: 0 }
  },
  // также может быть объявлено как
  // state: () => ({ count: 0 })
  actions: {
    increment() {
      this.count++
    },
  },
})

Затем вы используете его в компоненте:

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

const counter = useCounterStore()

counter.count++
// автозаполнение ✨
counter.$patch({ count: counter.count + 1 })
// или используете действие (action) вместо этого:
counter.increment()
</script>

<template>
  <!-- Обращайтесь к состоянию напрямую из хранилища -->
  <div>Current Count: {{ counter.count }}</div>
</template>

Попробуйте в песочнице

Вы даже можете использовать функцию (аналогичную setup() в компоненте) для определения хранилища для более сложных случаев использования:

js
export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  function increment() {
    count.value++
  }

  return { count, increment }
})

Попробуйте в песочнице

Если вы еще не знакомы с setup() и Composition API, не переживайте, Pinia также поддерживает набор map-помощников, аналогичных Vuex. Вы определяете хранилища так же, как и раньше, но затем используете mapStores(), mapState(), или mapActions():

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

const useUserStore = defineStore('user', {
  // ...
})

export default defineComponent({
  computed: {
    // другие вычисляемые свойства
    // ...
    // предоставляет доступ к this.counterStore и this.userStore
    ...mapStores(useCounterStore, useUserStore),
    // предоставляет доступ только для чтения к this.count и this.double
    ...mapState(useCounterStore, ['count', 'double']),
  },
  methods: {
    // предоставляет доступ к this.increment()
    ...mapActions(useCounterStore, ['increment']),
  },
})

Попробуйте в песочнице

Более подробную информацию о каждом из помощников для отображения (map helpers) вы найдете в разделе "Основные концепции".

Официальный курс

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

Почему Pinia

Pinia (произносится /piːnjʌ/, как "peenya" на английском) - это слово, наиболее близкое к "piña" (ананас на испанском), которое является допустимым именем пакета. На самом деле ананас - это группа отдельных цветов, которые объединяются, создавая несколько плодов. Подобно хранилищам, каждый из них рождается отдельно, но в конечном итоге все они соединяются. Это также вкусный тропический фрукт, происходящий из Южной Америки.

Более реалистичный пример

Вот более полный пример использования API, которое вы будете использовать с Pinia с типами даже в JavaScript. Для некоторых людей этого может быть достаточно, чтобы начать, даже без дальнейшего чтения, но мы всё же рекомендуем ознакомиться с остальной документацией или даже пропустить этот пример и вернуться к нему после прочтения всех Основных концепций.

js
import { defineStore } from 'pinia'

export const useTodos = defineStore('todos', {
  state: () => ({
    /** @type {{ text: string, id: number, isFinished: boolean }[]} */
    todos: [],
    /** @type {'all' | 'finished' | 'unfinished'} */
    filter: 'all',
    // тип будет автоматически приведен к числу
    nextId: 0,
  }),
  getters: {
    finishedTodos(state) {
      // автозаполнение! ✨
      return state.todos.filter((todo) => todo.isFinished)
    },
    unfinishedTodos(state) {
      return state.todos.filter((todo) => !todo.isFinished)
    },
    /**
     * @returns {{ text: string, id: number, isFinished: boolean }[]}
     */
    filteredTodos(state) {
      if (this.filter === 'finished') {
        // вызов других геттеров (getters) с автозаполнением ✨
        return this.finishedTodos
      } else if (this.filter === 'unfinished') {
        return this.unfinishedTodos
      }
      return this.todos
    },
  },
  actions: {
    // любое количество аргументов, возвращает Promise или нет
    addTodo(text) {
      // можно изменять состояние напрямую
      this.todos.push({ text, id: this.nextId++, isFinished: false })
    },
  },
})

Попробуйте в песочнице

Сравнение с Vuex

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

По сравнению с Vuex, Pinia предоставляет более простой API с меньшим количеством формальностей, предлагает API в стиле Composition API и, что самое важное, обеспечивает надежную поддержку вывода типов при использовании TypeScript.

RFCs

Изначально Pinia не проходила через процесс RFC (Request for Comments). Я тестировал идеи, опираясь на свой опыт разработки приложений, изучение код других разработчиков, работу с клиентами, использующими Pinia, и ответы на вопросы в Discord. Это позволило мне предложить решение, которое работает и адаптировано к различным случаям и размерам приложений. Ранее я часто публиковал обновления и развивал библиотеку, сохраняя её основной API неизменным.

Теперь, когда Pinia стала рекомендуемым средством управления состоянием, она подлежит тому же процессу RFC, что и другие основные библиотеки в экосистеме Vue, и её API находится в стабильном состоянии.

Сравнение с Vuex 3.x/4.x

Vuex 3.x - это Vuex для Vue 2, а Vuex 4.x - для Vue 3

API Pinia сильно отличается от Vuex ≤4, а именно:

  • мутаций (mutations) больше не существуют. Их часто воспринимали как чрезмерно многословные. Изначально они вносили интеграцию с Devtools, но теперь это больше не является проблемой.
  • Не нужно создавать сложные пользовательские обёртки для поддержки TypeScript; всё типизировано, и API разработано таким образом, чтобы максимально использовать вывод типов в TypeScript.
  • Больше нет магических строк для инъекции; импортируйте функции, вызывайте их и наслаждайтесь автозаполнением!
  • Нет необходимости динамически добавлять хранилища; они все являются динамическими по умолчанию, и вы даже не заметите этого. Обратите внимание, что вы всё равно можете вручную использовать хранилище для его регистрации в любое время, но поскольку это происходит автоматически, вам не нужно об этом беспокоиться.
  • Больше нет вложенного структурирования модулей (modules). Вы по-прежнему можете неявно вкладывать хранилища, импортируя и используя одно хранилище внутри другого, но Pinia предлагает плоскую структуру по дизайну, сохраняя при этом возможности перекрестной композиции между хранилищами. Вы даже можете иметь циклические зависимости между хранилищами.
  • Нет модулей с пространством имён (namespaced modules). Учитывая плоскую архитектуру хранилищ, "пространство имён" хранилищ встроено в то, как они определяются, и можно сказать, что все хранилища имеют свои собственные пространства имён.

Для более подробных инструкций о том, как преобразовать существующий проект с использованием Vuex ≤4 для использования Pinia, смотрите Руководство по миграции с Vuex.

Released under the MIT License.