Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Pinia: действия и работа с хранилищем

Действие (action) — функция хранилища, которая меняет его данные. Код изменения живёт в одном месте, а компоненты только вызывают действие: «загрузи статьи», «добавь статью». Действие может быть асинхронным и обращаться к API.

Действия в хранилище

Дополните src/stores/posts.js загрузкой и добавлением статей:

// src/stores/posts.js
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
 
const API = 'https://jsonplaceholder.typicode.com/posts'
 
export const usePostsStore = defineStore('posts', () => {
  const posts = ref([])
  const loading = ref(false)
  const error = ref(null)
 
  const count = computed(() => posts.value.length)
 
  async function fetchPosts() {
    loading.value = true
    error.value = null
    try {
      const response = await fetch(`${API}?_limit=10`)
      if (!response.ok) throw new Error(`Сервер ответил ${response.status}`)
      posts.value = await response.json()
    } catch (e) {
      error.value = e.message
    } finally {
      loading.value = false
    }
  }
 
  function addPost(title, body) {
    const id = Math.max(0, ...posts.value.map((p) => p.id)) + 1
    posts.value.unshift({ id, title, body })
  }
 
  return { posts, loading, error, count, fetchPosts, addPost }
})

Действия — обычные функции внутри defineStore. Их нужно вернуть вместе с данными, иначе компоненты их не увидят.

Вызов действия и storeToRefs

Перепишите src/components/PostList.vue на хранилище:

<!-- src/components/PostList.vue -->
<script setup>
import { onMounted } from 'vue'
import { storeToRefs } from 'pinia'
import { usePostsStore } from '../stores/posts.js'
 
const postsStore = usePostsStore()
const { posts, loading, error } = storeToRefs(postsStore)
 
onMounted(() => {
  if (posts.value.length === 0) postsStore.fetchPosts()
})
</script>
 
<template>
  <p v-if="loading">Загрузка...</p>
  <p v-else-if="error">Ошибка: {{ error }}</p>
  <ul v-else>
    <li v-for="post in posts" :key="post.id">
      <RouterLink :to="{ name: 'post', params: { id: post.id } }">{{ post.title }}</RouterLink>
    </li>
  </ul>
</template>

На главной появятся десять статей из API. storeToRefs превращает данные хранилища в отдельные ref, которые можно деструктурировать без потери реактивности. Действия берут прямо из хранилища — postsStore.fetchPosts(). Проверка длины не даёт загружать список заново при каждом возвращении на главную.

Форма новой статьи

Замените src/views/NewPostView.vue:

<!-- src/views/NewPostView.vue -->
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { usePostsStore } from '../stores/posts.js'
 
const postsStore = usePostsStore()
const router = useRouter()
const title = ref('')
const body = ref('')
 
function save() {
  if (title.value.trim() === '') return
  postsStore.addPost(title.value.trim(), body.value)
  router.push({ name: 'home' })
}
</script>
 
<template>
  <h2>Новая статья</h2>
  <form @submit.prevent="save">
    <input v-model="title" placeholder="Заголовок">
    <textarea v-model="body" placeholder="Текст"></textarea>
    <button>Опубликовать</button>
  </form>
</template>

Откройте /admin/new, введите заголовок и нажмите «Опубликовать» — откроется главная, и новая статья окажется первой в списке, а счётчик в шапке увеличится. Статья хранится только в памяти браузера: тестовый API не сохраняет данные, поэтому после перезагрузки она пропадёт.

Что куда класть

ГдеЧто
ХранилищеДанные, нужные нескольким страницам, и код их изменения
КомпонентСостояние формы, открыто ли меню — то, что нужно только ему
КомпозаблЛогика без общего состояния: таймер, useFetch

Частая ошибка

Передать действие через storeToRefs: const { fetchPosts } = storeToRefs(postsStore). storeToRefs берёт только данные и вычисляемые значения, функции он пропускает — fetchPosts окажется undefined. Действия вызывайте через хранилище или деструктурируйте его напрямую: const { fetchPosts } = postsStore.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.