Vuex и управление состоянием в Vue.js

Vuex и управление состоянием в Vue.js Vue.js

Vuex — это паттерн управления состоянием и библиотека для Vue.js приложений. Он служит централизованным хранилищем данных для всех компонентов приложения с правилами, гарантирующими, что состояние может быть изменено только предсказуемым образом

Архитектура Vuex

Vuex использует однонаправленный поток данных:

Components → Actions → Mutations → State → Components

Основные концепции:

  1. State — единое источник истины, содержащий все данные приложения
  2. Getters — вычисляемые свойства хранилища
  3. Mutations — единственный способ изменения состояния
  4. 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

Оцените статью
Уроки программирования
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest
0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x