Идеальный Store в Pinia (Vue)
- среда, 8 апреля 2026 г. в 00:00:10
Если спустя время у вас происходило такое, что компонент-стор разрастался, а каждое добавление или исправление логики уже пугало, то эта статья для вас.
Сегодня я не буду читать лекций. Просто поделюсь тем, как сам дошел до более-менее устойчивого подхода после того, как неоднократно рефакторил чужие сторы и ловил себя на мысли: «Зачем я это трогал?». Возможно, где-то я буду неправ, возможно, в вашем проекте это не взлетит. Я не напрашиваюсь в учителя, просто фиксирую опыт, чтобы не наступать на одни и те же грабли.

Встречал код примерно такого вида:
const docStore = useDocumentStore() const { client } = storeToRefs(docStore) client.value = null // Боль
Проблема не в том, что это сломается сразу. Проблема в том, что через полгода вы будете искать, кто и почему обнулил client, а логика изменения размазана по пяти компонентам.
Правильнее вынести мутации в экшены самого стора:
// в store export const useDocumentStore = defineStore('documents', () => { const client = ref(null) const resetClient = () => { client.value = null } return { client, resetClient } }) // в компоненте docStore.resetClient()
Так у вас всегда одна точка входа. Если завтра добавится логирование, валидация или отправка события в аналитику - вы поменяете только один метод, а не будете бегать по всему проекту.
Options API для сторов остался в прошлом. defineStore(() => { ... }) даёт всё, что нужно: прозрачную реактивность, возможность выносить хуки в отдельные композаблы и, что важно, гораздо проще тестируется.
Если вы до сих пор пишете state: () => ({ ... }), actions: { ... } - попробуйте переписать хотя бы один новый стор. Разница заметна сразу: меньше шаблонного кода, нет this, реактивность работает «как в компонентах».
Честно говоря, я стараюсь вообще не писать watch в сторах. Когда состояние меняется, запускать скрытые сайд-эффекты - это верный путь к отладочному аду.
В 90% случаев вотч можно заменить методом, который явно вызывает компонент или другой стор. Если нужно реагировать на изменения роута, URL-параметры или внешние события - делайте это в компоненте или в отдельном композабле, а в стор просто передавайте готовые данные через экшен. Стор должен быть «тупым» хранилищем, а не диспетчером событий.
Если стору нужно переживать перезагрузку страницы, не пишите localStorage.setItem в каждом экшене.
Либо используйте проверенный pinia-plugin-persistedstate, либо напишите свою обёртку с чётким интерфейсом и тестами. Главное - чтобы логика синхронизации не перемешивалась с бизнес-логикой.
Это, пожалуй, самый важный пункт. Стор должен заниматься только одним: читать, сохранять и обновлять данные. Не нужно тащить туда подписки на роутер, парсинг токенов, работу с веб-сокетами или расчёты, которые зависят от трёх других сторов.
Если логика начинает усложняться - выносите её. Стор останется чистым, его будет легко мокать в тестах, а рефакторинг перестанет вызывать панику.
Простой чек-лист перед коммитом: «Могу ли я заменить этот стор на обычный файл с константами и функциями, не сломав архитектуру?» Если ответ «нет» - скорее всего, вы перегрузили его ответственностью.
Идеального стора не существует. Есть только тот, который не вызывает желания удалить его при следующем открытии IDE. Держите их маленькими, явно описывайте контракты экшенов и не бойтесь выносить сложность наружу. Со временем это превращается в привычку, а проект начинает дышать.
Если у вас есть свои правила или вы категорически не согласны с каким-то пунктом - пишите. Обсудим, может, я что-то упустил или ваш кейс просто требует другого подхода.