Настройка State Management (Vuex) для Vue-приложения
Vuex — стандартный state manager для Vue 2 и ранних проектов на Vue 3. Основан на flux-архитектуре: state, getters, mutations, actions, modules. Мутировать состояние можно только через mutations, что гарантирует трассируемость изменений.
Если проект уже использует Vuex или работает на Vue 2 — настраиваем и оптимизируем именно его. Для новых проектов на Vue 3 предпочтительнее Pinia, но Vuex 4 полностью совместим с Vue 3.
Что входит в работу
Установка Vuex 4, проектирование модулей, типизация стора через TypeScript, async actions, namespace-модули, helpers, DevTools, тестирование.
Установка
# Vue 3
npm install vuex@4
# Vue 2
npm install vuex@3
// main.ts (Vue 3)
import { createApp } from 'vue'
import store from './store'
createApp(App).use(store).mount('#app')
Типизированный стор — корневой модуль
// store/types.ts
export interface RootState {
version: string
}
// store/index.ts
import { createStore } from 'vuex'
import type { RootState } from './types'
import { cart } from './modules/cart'
import { auth } from './modules/auth'
export default createStore<RootState>({
state: {
version: '1.0.0',
},
modules: {
cart,
auth,
},
})
Модуль cart
// store/modules/cart.ts
import type { Module } from 'vuex'
import type { RootState } from '../types'
interface CartState {
items: CartItem[]
loading: boolean
error: string | null
}
export const cart: Module<CartState, RootState> = {
namespaced: true,
state: () => ({
items: [],
loading: false,
error: null,
}),
getters: {
total: (state) =>
state.items.reduce((sum, i) => sum + i.price * i.quantity, 0),
count: (state) =>
state.items.reduce((sum, i) => sum + i.quantity, 0),
isEmpty: (state) => state.items.length === 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 })
}
},
REMOVE_ITEM(state, id: string) {
state.items = state.items.filter((i) => i.id !== id)
},
CLEAR_CART(state) {
state.items = []
},
SET_LOADING(state, loading: boolean) {
state.loading = loading
},
SET_ERROR(state, error: string | null) {
state.error = error
},
},
actions: {
addItem({ commit }, product: Product) {
commit('ADD_ITEM', product)
},
async checkout({ commit, state }) {
commit('SET_LOADING', true)
commit('SET_ERROR', null)
try {
await api.post('/orders', { items: state.items })
commit('CLEAR_CART')
} catch (err) {
commit('SET_ERROR', err instanceof Error ? err.message : 'Ошибка оформления')
} finally {
commit('SET_LOADING', false)
}
},
},
}
Модуль auth с Vue Router guard
// store/modules/auth.ts
export const auth: Module<AuthState, RootState> = {
namespaced: true,
state: () => ({
user: null,
token: localStorage.getItem('token'),
}),
getters: {
isAuthenticated: (state) => !!state.token,
currentUser: (state) => state.user,
hasRole: (state) => (role: string) => state.user?.roles?.includes(role) ?? false,
},
mutations: {
SET_AUTH(state, { user, token }: { user: User; token: string }) {
state.user = user
state.token = token
localStorage.setItem('token', token)
},
CLEAR_AUTH(state) {
state.user = null
state.token = null
localStorage.removeItem('token')
},
},
actions: {
async login({ commit }, credentials: LoginCredentials) {
const { data } = await axios.post<LoginResponse>('/api/auth/login', credentials)
commit('SET_AUTH', { user: data.user, token: data.token })
axios.defaults.headers.common['Authorization'] = `Bearer ${data.token}`
},
logout({ commit }) {
commit('CLEAR_AUTH')
delete axios.defaults.headers.common['Authorization']
},
async fetchCurrentUser({ commit, state }) {
if (!state.token) return
const { data } = await axios.get<User>('/api/me')
commit('SET_AUTH', { user: data, token: state.token })
},
},
}
Использование в компонентах
Composition API (Vue 3 + Vuex 4)
import { computed } from 'vue'
import { useStore } from 'vuex'
export default defineComponent({
setup() {
const store = useStore()
const total = computed(() => store.getters['cart/total'])
const isLoading = computed(() => store.state.cart.loading)
function addItem(product: Product) {
store.dispatch('cart/addItem', product)
}
function checkout() {
store.dispatch('cart/checkout')
}
return { total, isLoading, addItem, checkout }
},
})
Options API + helpers
import { mapState, mapGetters, mapActions } from 'vuex'
export default {
computed: {
...mapState('cart', ['items', 'loading']),
...mapGetters('cart', ['total', 'count', 'isEmpty']),
...mapGetters('auth', ['isAuthenticated']),
},
methods: {
...mapActions('cart', ['addItem', 'checkout']),
...mapActions('auth', ['logout']),
},
}
Типизация через typed store wrapper
Vuex 4 + TypeScript — болезненная комбинация без обёртки. Один из рабочих подходов:
// store/typed-store.ts
import { useStore as baseUseStore, Store } from 'vuex'
import type { InjectionKey } from 'vue'
import type { RootState } from './types'
export type AppStore = Store<RootState>
export const key: InjectionKey<AppStore> = Symbol()
export function useStore(): AppStore {
return baseUseStore(key)
}
// main.ts
import { key } from './store/typed-store'
app.use(store, key)
// в компоненте
import { useStore } from '@/store/typed-store'
const store = useStore() // типизированный
Плагин для персистентности
npm install vuex-persistedstate
import createPersistedState from 'vuex-persistedstate'
export default createStore({
plugins: [
createPersistedState({
paths: ['auth.token', 'ui.theme'],
storage: window.localStorage,
}),
],
// ...
})
Тестирование модулей
import { createStore } from 'vuex'
import { cart } from '@/store/modules/cart'
function buildStore() {
return createStore({ modules: { cart } })
}
test('ADD_ITEM увеличивает quantity для существующего товара', () => {
const store = buildStore()
const product = { id: '1', name: 'Test', price: 100 }
store.dispatch('cart/addItem', product)
store.dispatch('cart/addItem', product)
expect(store.getters['cart/count']).toBe(2)
expect(store.getters['cart/total']).toBe(200)
})
Структура проекта
src/
store/
index.ts # createStore + modules
types.ts # RootState и общие типы
typed-store.ts # типизированный useStore
modules/
auth.ts
cart.ts
ui.ts
notifications.ts
Что делаем
Настраиваем структуру модулей под предметную область, включаем namespace везде, добавляем TypeScript-типизацию через typed store wrapper, настраиваем persistedstate для нужных данных, покрываем тестами ключевые модули, подключаем Vue DevTools.
Срок: 2–3 дня, включая рефакторинг существующего кода при наличии.







