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.