Налаштування State Management (Vuex) для Vue-додатку
Коли SPA на Vue розростається, стан додатку стає десятками взаємозалежних змінних. Мутації розкидані по компонентах, а відстежити, хто і коли змінив дані, неможливо без логу дій. Наприклад, у проекті з 50 компонентами та 200 змінними стану будь-яка зміна може викликати ланцюгову реакцію багів. Ми часто стикаємося з такими проектами та налаштовуємо для них Vuex — стандартний state management для Vue. Він забезпечує строгий однонапрямлений потік даних: мутувати стан можна лише через мутації, що гарантує трасованість змін. Підходить і для Vue 2, і для Vue 3 (версія 4). Vuex не єдиний варіант: є Pinia, але для проектів на Vue 2 та в legacy-коді Vuex залишається стандартом де-факто. Налаштування Vuex включає кілька етапів, і з ним ви отримуєте передбачуваний стан та історію змін. Повна типізація Vuex і модульна архітектура — ключ до масштабування.
Які проблеми вирішуємо
- Порушення однонапрямленого потоку. Прямі зміни
stateз компонентів — типова помилка, що веде до непередбачуваної поведінки. Vuex фіксує всі мутації, і за допомогою DevTools можна за секунду знайти джерело. - Відсутність модульності. Весь стан в одному файлі (понад 1000 рядків) — складно підтримувати. Розбиваємо на модулі за предметними областями (cart, auth, ui), кожен зі своїм контекстом.
- Налагодження без DevTools. Без часової шкали мутацій неможливо зрозуміти, що призвело до багу. Vue DevTools показують історію змін і стан на будь-який момент, що скорочує час пошуку помилки в 2-3 рази.
- Відсутність типізації. JavaScript-стор без TypeScript — часті опечатки в іменах геттерів та екшенів. Впроваджуємо typed store wrapper, що дає автодоповнення і прибирає до 60% runtime-помилок. Вартість налаштування окупається за рахунок скорочення часу налагодження.
Як ми це робимо
Використовуємо Vuex 4 для Vue 3 або vuex@3 для Vue 2. Стек: TypeScript, модулі з namespaced: true, vuex-persistedstate для збереження токена та теми, Jest для тестування. Кожен модуль описуємо як типізований клас з інтерфейсом стану, геттерами, мутаціями та екшенами.
Порівняння Vuex та Pinia (для розуміння контексту)
| Критерій | Vuex | Pinia |
|---|---|---|
| Типізація | Потребує обгортки | З коробки |
| DevTools | Повна підтримка | Повна підтримка |
| Мутації | Обов'язкові | Не потрібні |
| Сумісність | Vue 2 та Vue 3 | Тільки Vue 3 |
| Розмір | ~10 КБ | ~2 КБ |
Vuex залишається затребуваним для великих проектів на Vue 2 та міграцій. За нашими спостереженнями, він у 2–3 рази скорочує час на налагодження завдяки суворим правилам та DevTools. А економія бюджету на підтримку може сягати 40%.
Що ви отримаєте після налаштування
| Компонент | Результат |
|---|---|
| Архітектура стору | Модульна структура з 3–7 модулів з чіткими межами |
| Типізація | Повна типізація з автосправленням в IDE |
| Persisted state | Збереження токена та теми в localStorage |
| Тести | Покриття ключових модулів модульними тестами (Jest) |
| Документація | README з описом модулів та прикладами використання |
Як правильно структурувати модулі Vuex?
Кожен модуль — це ізольований контекст. Приклад модуля cart:
// store/modules/cart.ts
import type { Module } from 'vuex'
import type { RootState } from '../types'
interface CartState {
items: CartItem[]
loading: boolean
}
export const cart: Module<CartState, RootState> = {
namespaced: true,
state: () => ({
items: [],
loading: false,
}),
getters: {
total: (state) => state.items.reduce((sum, i) => sum + i.price * i.quantity, 0),
},
mutations: {
ADD_ITEM(state, product: Product) {
const existing = state.items.find(i => i.id === product.id)
if (existing) {
existing.quantity++
} else {
state.items.push({ ...product, quantity: 1 })
}
},
CLEAR_CART(state) {
state.items = []
},
},
actions: {
async checkout({ commit, state }) {
commit('SET_LOADING', true)
await api.post('/orders', { items: state.items })
commit('CLEAR_CART')
commit('SET_LOADING', false)
},
},
} Чому варто використовувати typed store wrapper?
Для повної типізації створюємо обгортку useStore:
// store/typed-store.ts
import { useStore as baseUseStore, Store } from 'vuex'
import type { InjectionKey } from 'vue'
import type { RootState } from './types'
export const key: InjectionKey<Store<RootState>> = Symbol()
export function useStore(): Store<RootState> {
return baseUseStore(key)
}Підключаємо в main.ts і використовуємо в компонентах:
import { useStore } from '@/store/typed-store' const store = useStore() // повністю типізований Завдяки typed store wrapper автодоповнення працює в будь-якому компоненті, а помилки типів відловлюються на етапі компіляції.
Як тестувати модулі Vuex?
Перевіряємо ключові сценарії: додавання товару, збільшення кількості, оформлення замовлення.
import { createStore } from 'vuex'
import { cart } from '@/store/modules/cart'
test('додавання товару збільшує лічильник', () => {
const store = createStore({
modules: { cart }
})
store.dispatch('cart/addItem', { id: '1', price: 100 })
expect(store.getters['cart/count']).toBe(1)
})Такий підхід гарантує, що зміни в сторі не ламають логіку роботи.
Приклад реального кейсу
На одному з проектів (інтернет-магазин на Vue 2) ми зіткнулися з тим, що стан корзини був розмазаний по 12 компонентах. Після впровадження модуля cart з persisted state час пошуку помилок скоротився в 3 рази, а кодова база зменшилася на 15%.Процес роботи
- Аналіз — вивчаємо поточний код, виявляємо проблемні місця.
- Проектування — визначаємо модулі, типи, інтерфейси.
- Реалізація — пишемо стор, типізацію, persisted state.
- Інтеграція — підключаємо до компонентів, замінюємо локальний стан.
- Тестування — покриваємо модульні тести та інтеграцію.
- Деплой — перевіряємо в продакшені, фікс багів.
Терміни орієнтовно — від 2 до 5 днів залежно від обсягу legacy-коду. Вартість розраховується індивідуально. Ми займаємося розробкою на Vue більше 5 років і реалізували понад 30 проектів з Vuex, тому знаємо всі підводні камені. Отримайте консультацію з налаштування Vuex для вашого проекту — ми оцінимо код і запропонуємо оптимальну архітектуру. Замовте налаштування під ключ — ми гарантуємо прозору історію змін і легку підтримку.
Типові помилки при налаштуванні Vuex
Не забувайте namespaced: true — без нього модулі конфліктують. Типізуйте мутації, використовуючи константи або TypeScript enum. Не зберігайте в стані дані, які можна обчислити — застосовуйте гетери. Не викликайте мутації з компонентів безпосередньо — тільки через екшени. Ці прості правила прибирають до 70% багів на старті.







