Простой блог на Pinia и Vue.js. Сложный state management

Управление состояниями — один из краеугольных элементов в разработке веб-приложений; любое нетривиальное приложение нуждается в управлении состояниями. В течение многих лет Vuex был де-факто инструментом управления состояниями для приложений Vue.

Однако в новой документации по Vue официально рекомендуется другой инструмент: Pinia. Но прежде чем вы скажете: «О, нет, только не еще один инструмент для изучения», вы должны знать, что Pinia — это де-факто Vuex 5, как написал Эван Ю в этом твите:

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

  • Pinia против Vuex
  • Использование базового хранилища Pinia
  • Начало работы с Pinia
  • Определение хранилищ приложений в Pinia
    • Определение хранилища постов
    • Определение хранилища комментариев
    • Определение хранилища авторов
  • Создание представлений и компонентов в Pinia
    • Создание представления постов
    • Создание представления одного поста
    • Создание представления авторов
    • Создание представления одного автора
  • Настройка маршрутизатора
  • Проверка хранилищ Pinia в Vue Devtools

Проект, который мы создадим по ходу дела, продемонстрирует основы построения приложений со сложным состоянием. Но сначала давайте посмотрим, чем Pinia отличается от Vuex.

Pinia vs. Vuex

Хотя Pinia можно считать Vuex 5, между ними есть несколько важных различий, о которых следует помнить:

  • В Pinia мутации удалены из-за их крайней многословности (verbosity).
  • Pinia полностью поддерживает TypeScript и предлагает автодополнение кода JavaScript.
  • Pinia не нуждается во вложенных модулях, но если одно хранилище использует другое хранилище, это можно считать неявным вложением.
  • В Pinia нет необходимости в пространстве имен хранилищ приложений, как для модулей Vuex.
  • Pinia использует Composition API, но может использоваться и с Options API.
  • Pinia предлагает поддержку рендеринга на стороне сервера (SSR).
  • Pinia можно использовать в Vue 2 или Vue 3 (оба варианта с поддержкой devtools).

Использование базового хранилища Pinia

API Pinia максимально упрощен. Вот пример базового хранилища Pinia:

import { defineStore } from 'pinia' export const useCounterStore = defineStore({ id: 'counter', state: () => ({ counter: 0 }), getters: { doubleCount: (state) => state.counter * 2 }, actions: { increment() { this.counter++ } } })
Code language: JavaScript (javascript)

Чтобы определить хранилище, мы используем функцию defineStore. Здесь слово define используется вместо create, потому что хранилище не создается до тех пор, пока он фактически не используется в компоненте/странице.

Начало имени хранилища с use — это соглашение для всех композиций (composables). Каждое хранилище должно предоставить уникальный id для монтирования хранилища в devtools.

Pinia также использует понятия state, getters и actions, которые эквивалентны data, computed и methods в компонентах:

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

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

Увидев, насколько прост API Pinia, давайте приступим к созданию нашего проекта.

Начало работы с Pinia

Чтобы продемонстрировать возможности Pinia, мы создадим базовый движок блога со следующими функциями:

  • Список всех постов
  • Страница одного поста с комментариями к нему
  • Список всех авторов постов
  • Страница одного автора с написанными им постами

Сначала создадим новый проект Vue, выполнив следующую команду:

npm init vue@latest
Code language: CSS (css)

Это позволит установить и выполнить create-vue, официальный инструмент для создания проектов Vue, для настройки нового проекта с Vue и Vite. В процессе вы должны выбрать инструменты, необходимые для проекта:

Выберите все инструменты, отмеченные красной стрелкой: Router, Pinia, ESLint и Prettier. Когда установка завершится, перейдите в проект и установите зависимости:

cd vue-project npm install

И теперь вы можете открыть проект в браузере, выполнив следующее:

npm run dev

Ваше новое приложение Vue будет обслуживаться по адресу http://localhost:3000. Вот что вы должны увидеть:

Теперь, чтобы адаптировать его к нашим потребностям, мы очистим структуру проекта по умолчанию. Вот как она выглядит сейчас и что мы удалим.

Для этого сначала закройте терминал и удалите все файлы/папки в пределах красных границ.

Теперь мы готовы приступить к написанию кода проекта.

Давайте сначала откроем файл main.js, чтобы увидеть, как создается и включается в проект корневое хранилище Pinia:

import { createApp } from 'vue' import { createPinia } from 'pinia' // Import import App from './App.vue' import router from './router' const app = createApp(App) app.use(createPinia()) // Create the root store app.use(router) app.mount('#app')
Code language: JavaScript (javascript)

Как вы можете видеть, функция createPinia импортируется, создает хранилище Pinia и передает его приложению.

Теперь откройте файл App.vue и замените его содержимое на следующее:

<script setup> import { RouterLink, RouterView } from 'vue-router' </script> <template> <header class="navbar"> <div> <nav> <RouterLink to="/">Posts</RouterLink> - <RouterLink to="/authors">Authors</RouterLink> </nav> </div> </header> <RouterView /> </template> <style> .navbar { background-color: lightgreen; padding: 1.2rem; } </style>
Code language: HTML, XML (xml)

Здесь мы изменили метки ссылок, заменив Home на Posts и About на Authors.

Мы также изменили ссылку Authors с /about на /authors, удалили все стили по умолчанию и добавили свои собственные для класса navbar, который мы добавляем, чтобы отличать навигацию от постов.

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

Определение хранилищ приложений в Pinia

Для нашего небольшого приложения мы будем использовать службу JSONPlaceholder в качестве источника данных и эти три ресурса: users, posts, и comments.

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

Как вы видите, пользователи связаны с постами по его id, а посты связаны с комментариями таким же образом. Таким образом, чтобы получить автора поста, мы можем использовать userId, а чтобы получить комментарии к посту, мы можем использовать postId.

Имея эти знания, мы можем начать сопоставлять данные с нашими хранилищами.

Определение хранилища постов

Первое хранилище, который мы определим, предназначен для записей блога. В каталоге stores переименуйте counter.js в post.js и замените его содержимое на следующее:

import { defineStore } from 'pinia' export const usePostStore = defineStore({ id: 'post', state: () => ({ posts: [], post: null, loading: false, error: null }), getters: { getPostsPerAuthor: (state) => { return (authorId) => state.posts.filter((post) => post.userId === authorId) } }, actions: { async fetchPosts() { this.posts = [] this.loading = true try { this.posts = await fetch('https://jsonplaceholder.typicode.com/posts') .then((response) => response.json()) } catch (error) { this.error = error } finally { this.loading = false } }, async fetchPost(id) { this.post = null this.loading = true try { this.post = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`) .then((response) => response.json()) } catch (error) { this.error = error } finally { this.loading = false } } } })
Code language: JavaScript (javascript)

Давайте разделим это на небольшие фрагменты и объясним, что происходит. Во-первых, мы определяем usePostStore с id post.

Во-вторых, мы определяем наше state с помощью четырех свойств:

  • posts для хранения найденных постов
  • post для хранения текущего поста
  • loading для хранения состояния загрузки
  • error для хранения ошибки, если таковая существует

В-третьих, мы создаем геттер для получения информации о том, сколько постов написал автор. По умолчанию геттер принимает state в качестве аргумента и использует его для получения доступа к массиву posts. Геттеры не могут принимать пользовательские аргументы, но мы можем вернуть функцию, которая может принимать такие аргументы.

Итак, в нашей функции геттер мы фильтруем posts, чтобы найти все посты с определенным идентификатором пользователя. Этот ID мы предоставим позже, когда будем использовать его в компоненте.

Однако обратите внимание, что когда мы возвращаем функцию с аргументом из геттер, геттер больше не кэшируется.

Наконец, давайте создадим два асинхронных действия для получения всех постов и одного поста.

В действии fetchPosts() мы сначала сбрасываем posts и устанавливаем loading в true. Затем мы получаем посты, используя FetchAPI и ресурс постов из JSONPlaceholder. Если возникла ошибка, мы присваиваем ее свойству error. И, наконец, возвращаем loading в false.

Действие fetchPost(id) почти идентично, но на этот раз мы используем свойство post и указываем id, чтобы получить один пост; убедитесь, что вы используете обратный апостроф вместо одинарных кавычек при получении поста.

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

У нас есть посты, теперь пришло время получить комментарии.

Определение хранилища комментариев

В директории stores создайте файл comment.js со следующим содержанием:

import { defineStore } from 'pinia' import { usePostStore } from './post' export const useCommentStore = defineStore({ id: 'comment', state: () => ({ comments: [] }), getters: { getPostComments: (state) => { const postSore = usePostStore() return state.comments.filter((post) => post.postId === postSore.post.id) } }, actions: { async fetchComments() { this.comments = await fetch('https://jsonplaceholder.typicode.com/comments') .then((response) => response.json()) } } })
Code language: JavaScript (javascript)

Здесь мы создаем свойство массива comments в state для хранения полученных комментариев. Мы получаем их с помощью действия fetchComments().

Интересной частью здесь является геттер getPostComments. Чтобы получить комментарии к посту, нам нужен ID текущего поста. Поскольку он уже есть в хранилище постов, можем ли мы получить его оттуда?

Да, к счастью, Pinia позволяет нам использовать одно хранилище в другом и наоборот. Итак, чтобы получить ID поста, мы импортируем usePostStore и используем его в геттере getPostComments.

Итак, теперь у нас есть комментарии; осталось получить авторов.

Определение хранилища авторов

В каталоге stores создайте файл author.js со следующим содержанием:

import { defineStore } from 'pinia' import { usePostStore } from './post' export const useAuthorStore = defineStore({ id: 'author', state: () => ({ authors: [] }), getters: { getPostAuthor: (state) => { const postStore = usePostStore() return state.authors.find((author) => author.id === postStore.post.userId) } }, actions: { async fetchAuthors() { this.authors = await fetch('https://jsonplaceholder.typicode.com/users') .then((response) => response.json()) } } })
Code language: JavaScript (javascript)

Это практически идентично commentStore. Мы снова импортируем usePostStore и используем его для предоставления необходимого ID автора в геттере getPostAuthor.

Вот и все. Вы видите, как легко создавать хранилища с Pinia — простым и элегантным решением.

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

Создание представлений и компонентов в Pinia

В этом разделе мы создадим необходимые представления и компоненты для применения хранилищ Pinia, которые мы только что создали. Начнем со списка всех постов.

Обратите внимание, что я использую Pinia с API Composition и <script setup>syntax. Если вы хотите вместо этого использовать API Options, ознакомьтесь с этим руководством.

Создание представления постов

В каталоге views переименуйте HomeView.vue в PostsView.vue и замените его содержимое на следующее:

<script setup> import { RouterLink } from 'vue-router' import { storeToRefs } from 'pinia' import { usePostStore } from '../stores/post' const { posts, loading, error } = storeToRefs(usePostStore()) const { fetchPosts } = usePostStore() fetchPosts() </script> <template> <main> <p v-if="loading">Loading posts...</p> <p v-if="error">{{ error.message }}</p> <p v-if="posts" v-for="post in posts" :key="post.id"> <RouterLink :to="`/post/${post.id}`">{{ post.title }}</RouterLink> <p>{{ post.body }}</p> </p> </main> </template>
Code language: HTML, XML (xml)

Обратите внимание, что если вы получите уведомление о том, что вы переименовали файл, просто проигнорируйте его.

Здесь мы импортируем и извлекаем все необходимые данные из хранилища post.

Мы не можем использовать destructuring для свойств состояния и геттеров, потому что они потеряют свою реактивность. Чтобы решить эту проблему, Pinia предоставляет утилиту storeToRefs, которая создает ссылку для каждого свойства. Действия могут быть извлечены напрямую без проблем.

Мы вызываем функцию fetchPosts() для получения постов. При использовании Composition API и вызове функции внутри функции setup() это эквивалентно использованию хука created(). Таким образом, мы получим посты до того, как компонент смонтируется.

В шаблоне также есть ряд директив v-if. Сначала мы показываем сообщение о загрузке, если загрузка true. Затем мы показываем сообщение об ошибке, если произошла ошибка.

Наконец, мы перебираем посты и отображаем заголовок и тело каждого из них. Мы используем компонент RouterLink для добавления ссылки к заголовку, чтобы при нажатии на нее пользователи переходили к представлению отдельного поста, которое мы создадим немного позже.

Теперь давайте изменим файл router.js. Откройте его и замените его содержимое на следующее:

import { createRouter, createWebHistory } from 'vue-router' import PostsView from '../views/PostsView.vue' const router = createRouter({ history: createWebHistory(), routes: [ { path: '/', name: 'posts', component: PostsView }, { path: '/about', name: 'about', // route level code-splitting // this generates a separate chunk (About.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import('../views/AboutView.vue') } ] }) export default router
Code language: JavaScript (javascript)

Здесь мы импортируем PostsView.vue и используем его в качестве компонента в первом маршруте. Мы также изменим название с home на posts.

Тестирование представления постов

Итак, пришло время проверить, чего мы достигли на данный момент. Запустите приложение (npm run dev) и посмотрите результат в браузере:

Вы, вероятно, получите несколько предупреждений Vue в консоли, начиная с «No match found…» Это потому, что мы еще не создали необходимые компоненты, и вы можете смело игнорировать их.

Вам также может потребоваться перезагрузить страницу, если посты не отображаются.

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

Создание представления одного поста

В каталоге views создайте файл PostView.vue со следующим содержимым:

<script setup> import { useRoute } from 'vue-router' import { storeToRefs } from 'pinia' import { useAuthorStore } from '../stores/author' import { usePostStore } from '../stores/post' import Post from '../components/Post.vue' const route = useRoute() const { getPostAuthor } = storeToRefs(useAuthorStore()) const { fetchAuthors} = useAuthorStore() const { post, loading, error } = storeToRefs(usePostStore()) const { fetchPost } = usePostStore() fetchAuthors() fetchPost(route.params.id) </script> <template> <div> <p v-if="loading">Loading post...</p> <p v-if="error">{{ error.message }}</p> <p v-if="post"> <post :post="post" :author="getPostAuthor"></post> </p> </div> </template>
Code language: HTML, XML (xml)

В настройке мы извлекаем getPostAuthor и fetchAuthors из хранилища авторов и необходимые данные из хранилища постов. Мы также вызываем fetchAuthors(), чтобы получить существующих авторов.

Далее мы вызываем действие fetchPost(route.params.id) с ID, предоставленным с помощью объекта маршрута. Это обновляет getPostAuthor, и мы можем эффективно использовать его в шаблоне.

Чтобы предоставить фактический пост, мы используем компонент post, который принимает два свойства: post и author. Давайте создадим компонент.

Создание компонента post

В каталоге components создайте файл Post.vue со следующим содержимым:

<script setup> import { RouterLink } from 'vue-router' import { storeToRefs } from 'pinia' import { useCommentStore } from '../stores/comment' import Comment from '../components/Comment.vue' defineProps(['post', 'author']) const { getPostComments } = storeToRefs(useCommentStore()) const { fetchComments } = useCommentStore() fetchComments() </script> <template> <div> <div> <h2>{{ post.title }}</h2> <p v-if="author">Written by: <RouterLink :to="`/author/${author.username}`">{{ author.name }}</RouterLink> | <span>Comments: {{ getPostComments.length }}</span> </p> <p>{{ post.body }}</p> </div> <hr> <h3>Comments:</h3> <comment :comments="getPostComments"></comment> </div> </template>
Code language: HTML, XML (xml)

Здесь мы определяем необходимые реквизиты с помощью функции defineProps и извлекаем необходимые данные из хранилища комментариев. Затем мы получаем комментарии, чтобы getPostComments мог быть обновлен должным образом.

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

Для отображения комментариев мы будем использовать отдельный компонент и передавать комментарии к посту в свойство comments.

Создание компонента comment

В каталоге components создайте файл Comment.vue со следующим содержанием:

<script setup> defineProps(['comments']) </script> <template> <div> <div v-for="comment in comments" :key="comment.id"> <h3>{{ comment.name }}</h3> <p>{{ comment.body }}</p> </div> </div> </template>
Code language: HTML, XML (xml)

Это довольно просто. Мы определяем свойство comments и используем его для итерационного просмотра комментариев поста.

Прежде чем мы снова протестируем приложение, добавьте следующее в файл router.js:

import PostView from '../views/PostView.vue' // ... routes: [ // ... { path: '/post/:id', name: 'post', component: PostView }, ]
Code language: JavaScript (javascript)

Запустите приложение снова. Вы должны увидеть аналогичный вид при переходе к одному посту:

Теперь пришло время отобразить авторов. Снова закройте терминал.

Создание представления авторов

В каталоге views переименуйте файл AboutView.vue в AuthorsView.vue и замените его содержимое на следующее:

<script setup> import { RouterLink } from 'vue-router' import { storeToRefs } from 'pinia' import { useAuthorStore } from '../stores/author' const { authors } = storeToRefs(useAuthorStore()) const { fetchAuthors } = useAuthorStore() fetchAuthors() </script> <template> <div> <p v-if="authors" v-for="author in authors" :key="author.id"> <RouterLink :to="`/author/${author.username}`">{{ author.name }}</RouterLink> </p> </div> </template>
Code language: HTML, XML (xml)

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

Снова откройте файл router.js и измените маршрут для страницы About на следующий:

{ path: '/authors', name: 'authors', // route level code-splitting // this generates a separate chunk (About.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import('../views/AuthorsView.vue') },
Code language: JavaScript (javascript)

Здесь мы изменим путь и имя на /authors и authors, соответственно, и импортируем AuthorsView.vue с lazy loading.

Запустите приложение снова. При переходе к просмотру авторов вы должны увидеть следующее:

Теперь давайте создадим представление с одним автором. Снова закройте терминал.

Создание представления одного автора

В каталоге views создайте файл AuthorView.vue со следующим содержимым:

<script setup> import { computed } from 'vue' import { useRoute } from 'vue-router' import { storeToRefs } from 'pinia' import { useAuthorStore } from '../stores/author' import { usePostStore } from '../stores/post' import Author from '../components/Author.vue' const route = useRoute() const { authors } = storeToRefs(useAuthorStore()) const { getPostsPerAuthor } = storeToRefs(usePostStore()) const { fetchPosts } = usePostStore() const getAuthorByUserName = computed(() => { return authors.value.find((author) => author.username === route.params.username) }) fetchPosts() </script> <template> <div> <author :author="getAuthorByUserName" :posts="getPostsPerAuthor(getAuthorByUserName.id)"> </author> </div> </template>
Code language: HTML, XML (xml)

Здесь, чтобы узнать, кто является текущим автором, мы используем его имя пользователя, получая его из маршрута. Для этого мы создаем вычисляемый getAuthorByUserName; мы передаем свойства author и posts компоненту author, который мы создадим прямо сейчас.

Создание компонента author

В каталоге components создайте файл Author.vue со следующим содержимым:

<script setup> import { RouterLink } from 'vue-router' defineProps(['author', 'posts']) </script> <template> <div> <h1>{{author.name}}</h1> <p>{{posts.length}} posts written.</p> <p v-for="post in posts" :key="post.id"> <RouterLink :to="`/post/${post.id}`">{{ post.title }}</RouterLink> </p> </div> </template>
Code language: HTML, XML (xml)

Этот компонент отображает имя автора, количество постов, написанных автором, и сами посты.

Затем добавьте следующее в файл router.js:

import AuthorView from '../views/AuthorView.vue' // ... routes: [ // ... { path: '/author/:username', name: 'author', component: AuthorView } ]
Code language: JavaScript (javascript)

Запустите приложение снова. При переходе к представлению автора вы должны увидеть следующее:

Настройка маршрутизатора

Вот как должен выглядеть окончательный файл router.js:

import { createRouter, createWebHistory } from 'vue-router' import PostsView from '../views/PostsView.vue' import PostView from '../views/PostView.vue' import AuthorView from '../views/AuthorView.vue' const router = createRouter({ history: createWebHistory(), routes: [ { path: '/', name: 'posts', component: PostsView }, { path: '/authors', name: 'authors', // route level code-splitting // this generates a separate chunk (About.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import('../views/AuthorsView.vue') }, { path: '/post/:id', name: 'post', component: PostView }, { path: '/author/:username', name: 'author', component: AuthorView }, ] })
Code language: JavaScript (javascript)

Теперь все предупреждения Vue об отсутствии ресурсов/компонентов должны исчезнуть.

Вот и все. Мы успешно создали и использовали хранилища Pinia в довольно сложном приложении.

Наконец, давайте посмотрим, как мы можем проверить приложение в Vue devtools.

Проверка хранилищ Pinia в Vue Devtools

На следующих скриншотах у нас открыт пост с ID 2. Вот как перечислены маршруты приложения на вкладке «Маршруты»:

Мы видим, что все созданные нами маршруты здесь, а маршрут для отдельного поста активен, потому что он используется в настоящее время.

Теперь переключимся на вкладку Components, чтобы изучить дерево компонентов приложения для представления поста:

Как мы видим, приложение начинается с двух компонентов RouretLink и компонента RouterView, определенных в App.vue. Затем у нас есть представление одного поста, за которым следует компонент post. В конце есть еще один RouterLink и компонент комментария.

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

Здесь находится хранилище post:

Мы видим, что Pinia показывает правильный открытый пост. То же самое верно и для хранилища автора:

И, наконец, хранилище комментариев показывает комментарии:

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

Теперь вы знаете, как создавать, использовать и проверять хранилища Pinia.

Заключение

Я очень доволен новым официальным инструментом управления состояниями Vue. Как мы видели, он модульный по дизайну, прост в использовании, имеет крошечный footprint (has a tiny footprint), и, наконец, что не менее важно, он простой, гибкий и мощный. Создавать хранилища с Pinia действительно приятно.

В этом туториале мы создали базовый движок блога, включающий основные возможности, предоставляемые Pinia (состояние, геттеры и действия). Конечно, проект можно расширить, добавив функциональность CRUD для авторов, постов и комментариев, но это выходит за рамки данного туториала.

При желании вы можете попробовать реализовать такую функциональность самостоятельно, чтобы отработать полученные знания. Руководство по JSONPlaceholder может помочь вам в этой работе.

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

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

Оцените ваши приложения Vue именно так, как это делает пользователь

Отладка приложений Vue.js может быть сложной задачей, особенно когда во время пользовательской сессии происходят десятки, а то и сотни изменений. Если вы заинтересованы в мониторинге и отслеживании изменений Vue для всех ваших пользователей на производстве, попробуйте LogRocket.

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

Плагин LogRocket Vuex регистрирует изменения Vuex в консоли LogRocket, предоставляя вам информацию о том, что привело к ошибке и в каком состоянии находилось приложение в момент возникновения проблемы.

Модернизируйте отладку приложений Vue — Начните мониторинг бесплатно.

Перевод: https://blog.logrocket.com/complex-vue-3-state-management-pinia/

Опубликовано
В рубрике Vue.js

Добавить комментарий

Ваш адрес email не будет опубликован.