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

Сегодня я не буду читать лекций. Просто поделюсь тем, как сам дошел до более-менее устойчивого подхода после того, как неоднократно рефакторил чужие сторы и ловил себя на мысли: «Зачем я это трогал?». Возможно, где-то я буду неправ, возможно, в вашем проекте это не взлетит. Я не напрашиваюсь в учителя, просто фиксирую опыт, чтобы не наступать на одни и те же грабли.

1. Не мутируйте состояние напрямую из компонентов

Встречал код примерно такого вида:

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()

Так у вас всегда одна точка входа. Если завтра добавится логирование, валидация или отправка события в аналитику - вы поменяете только один метод, а не будете бегать по всему проекту.

2. Используйте Setup Stores

Options API для сторов остался в прошлом. defineStore(() => { ... }) даёт всё, что нужно: прозрачную реактивность, возможность выносить хуки в отдельные композаблы и, что важно, гораздо проще тестируется.

Если вы до сих пор пишете state: () => ({ ... }), actions: { ... } - попробуйте переписать хотя бы один новый стор. Разница заметна сразу: меньше шаблонного кода, нет this, реактивность работает «как в компонентах».

3. Забудьте про watch внутри сторов

Честно говоря, я стараюсь вообще не писать watch в сторах. Когда состояние меняется, запускать скрытые сайд-эффекты - это верный путь к отладочному аду.

В 90% случаев вотч можно заменить методом, который явно вызывает компонент или другой стор. Если нужно реагировать на изменения роута, URL-параметры или внешние события - делайте это в компоненте или в отдельном композабле, а в стор просто передавайте готовые данные через экшен. Стор должен быть «тупым» хранилищем, а не диспетчером событий.

4. localStorage - только через плагин или обёртку

Если стору нужно переживать перезагрузку страницы, не пишите localStorage.setItem в каждом экшене.

Либо используйте проверенный pinia-plugin-persistedstate, либо напишите свою обёртку с чётким интерфейсом и тестами. Главное - чтобы логика синхронизации не перемешивалась с бизнес-логикой.

5. Стор != свалка логики

Это, пожалуй, самый важный пункт. Стор должен заниматься только одним: читать, сохранять и обновлять данные. Не нужно тащить туда подписки на роутер, парсинг токенов, работу с веб-сокетами или расчёты, которые зависят от трёх других сторов.

Если логика начинает усложняться - выносите её. Стор останется чистым, его будет легко мокать в тестах, а рефакторинг перестанет вызывать панику.

Простой чек-лист перед коммитом: «Могу ли я заменить этот стор на обычный файл с константами и функциями, не сломав архитектуру?» Если ответ «нет» - скорее всего, вы перегрузили его ответственностью.

Итог

Идеального стора не существует. Есть только тот, который не вызывает желания удалить его при следующем открытии IDE. Держите их маленькими, явно описывайте контракты экшенов и не бойтесь выносить сложность наружу. Со временем это превращается в привычку, а проект начинает дышать.

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