Vuex — это паттерн управления состоянием и библиотека для Vue.js приложений. Он служит централизованным хранилищем данных для всех компонентов приложения с правилами, гарантирующими, что состояние может быть изменено только предсказуемым образом
Архитектура Vuex
Vuex использует однонаправленный поток данных:
Components → Actions → Mutations → State → Components
Основные концепции:
- State — единое источник истины, содержащий все данные приложения
- Getters — вычисляемые свойства хранилища
- Mutations — единственный способ изменения состояния
- Actions — обработка асинхронных операций
Установка и базовая настройка
npm install vuex@next --save
// store/index.js
import { createStore } from 'vuex'
export default createStore({
state: {
count: 0,
todos: []
},
getters: {
// Геттеры
},
mutations: {
// Мутации
},
actions: {
// Действия
}
})
// main.js
import { createApp } from 'vue'
import store from './store'
import App from './App.vue'
const app = createApp(App)
app.use(store)
app.mount('#app')
State (Состояние)
State представляет собой единый объект, содержащий все данные приложения
const state = {
user: {
name: 'John',
email: 'john@example.com'
},
products: [],
cart: {
items: [],
total: 0
}
}
Доступ к state из компонента:
<template>
<div>
<p>User name: {{ $store.state.user.name }}</p>
<p>Cart total: {{ $store.state.cart.total }}</p>
</div>
</template>
<script>
export default {
computed: {
userName() {
return this.$store.state.user.name
}
}
}
</script>
Getters (Геттеры)
Геттеры позволяют получать производные данные из state:
const getters = {
cartItemCount: (state) => {
return state.cart.items.length
},
totalPrice: (state) => {
return state.cart.items.reduce((total, item) => {
return total + (item.price * item.quantity)
}, 0)
},
// Геттер с параметрами
getProductById: (state) => (id) => {
return state.products.find(product => product.id === id)
}
}
Использование в компоненте:
<template>
<div>
<p>Items in cart: {{ $store.getters.cartItemCount }}</p>
<p>Total: ${{ $store.getters.totalPrice }}</p>
<p>Product: {{ $store.getters.getProductById(1) }}</p>
</div>
</template>
Mutations (Мутации)
Мутации — единственный способ изменения состояния в Vuex:
const mutations = {
INCREMENT_COUNTER(state) {
state.count++
},
ADD_TO_CART(state, product) {
state.cart.items.push(product)
},
UPDATE_USER(state, userData) {
state.user = { ...state.user, ...userData }
}
}
Вызов мутаций:
// В компоненте
methods: {
addProduct() {
this.$store.commit('ADD_TO_CART', {
id: 1,
name: 'Product',
price: 99
})
}
}
Actions (Действия)
Действия используются для асинхронных операций:
const actions = {
async fetchProducts({ commit }) {
try {
const response = await axios.get('/api/products')
commit('SET_PRODUCTS', response.data)
} catch (error) {
console.error(error)
}
},
async checkout({ commit, state }) {
const items = state.cart.items
try {
await api.checkout(items)
commit('CLEAR_CART')
} catch (error) {
commit('SET_CHECKOUT_ERROR', error)
}
}
}
Вызов действий:
// В компоненте
methods: {
async loadProducts() {
await this.$store.dispatch('fetchProducts')
}
}
Вспомогательные функции
mapState
import { mapState } from 'vuex'
export default {
computed: {
...mapState({
user: state => state.user,
cartItems: state => state.cart.items
})
}
}
mapGetters
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters([
'cartItemCount',
'totalPrice'
])
}
}
Модули
Для больших приложений можно разделить store на модули:
// store/modules/cart.js
export default {
namespaced: true,
state: {
items: []
},
getters: {
itemCount: state => state.items.length
},
mutations: {
ADD_ITEM(state, item) {
state.items.push(item)
}
},
actions: {
addToCart({ commit }, item) {
commit('ADD_ITEM', item)
}
}
}
// store/index.js
import cart from './modules/cart'
export default createStore({
modules: {
cart
}
})
Работа с локальным хранилищем
Пример плагина для сохранения состояния в localStorage:
const localStoragePlugin = store => {
// Загрузка состояния при инициализации
const savedState = localStorage.getItem('vuex-state')
if (savedState) {
store.replaceState(JSON.parse(savedState))
}
// Сохранение состояния при изменении
store.subscribe((mutation, state) => {
localStorage.setItem('vuex-state', JSON.stringify(state))
})
}
export default createStore({
// ...
plugins: [localStoragePlugin]
})
Практический пример полного store
import { createStore } from 'vuex'
export default createStore({
state: {
user: null,
products: [],
cart: {
items: [],
total: 0
},
loading: false,
error: null
},
getters: {
isAuthenticated: state => !!state.user,
cartItemCount: state => state.cart.items.length,
cartTotal: state => state.cart.items.reduce((total, item) =>
total + item.price * item.quantity, 0
)
},
mutations: {
SET_USER(state, user) {
state.user = user
},
SET_PRODUCTS(state, products) {
state.products = products
},
ADD_TO_CART(state, product) {
const existingItem = state.cart.items.find(item =>
item.id === product.id
)
if (existingItem) {
existingItem.quantity++
} else {
state.cart.items.push({
...product,
quantity: 1
})
}
},
SET_LOADING(state, status) {
state.loading = status
},
SET_ERROR(state, error) {
state.error = error
}
},
actions: {
async login({ commit }, credentials) {
try {
commit('SET_LOADING', true)
const user = await api.login(credentials)
commit('SET_USER', user)
} catch (error) {
commit('SET_ERROR', error.message)
} finally {
commit('SET_LOADING', false)
}
},
async fetchProducts({ commit }) {
try {
commit('SET_LOADING', true)
const products = await api.getProducts()
commit('SET_PRODUCTS', products)
} catch (error) {
commit('SET_ERROR', error.message)
} finally {
commit('SET_LOADING', false)
}
}
}
})
Для закрепления материала рекомендуется выполнить практические задания и создать небольшое приложение с использованием Vuex
Еще больше уроков для изучения Vue