JavaScript

Освоение Vuex. От простого к сложному.

Spread the love

Официальная документация Vuex определяет его как шаблон управления состоянием. Но что это значит? Что такое шаблон управления состоянием?

Представьте себе работу над большим веб-приложением с сотнями маршрутов и компонентов. Разве не было бы проще, если бы мы могли хранить все данные, которые нам когда-либо понадобятся, внутри приложения в одном централизованном хранилище?

Каждый компонент или маршрут внутри нашего приложения будет запрашивать данные из хранилища Vuex и передавать измененные данные обратно в это хранилище.

По сути, Vuex можно рассматривать как единый источник данных для всего приложения.

Данные хранятся внутри хранилища в виде объекта JSON. Например:

state : {
    name : “John Doe”,
    age : “28”,
}

Но как наши компоненты и маршруты могут получить доступ к данным? Для этого мы должны определить Геттеры (Getters) внутри нашего хранилища Vuex. Давайте посмотрим, как выглядит простой Геттер (Getter), для получения имени из нашего хранилища.

getters : {
  NAME : state => {
    return state.name
  }
}

Обратите внимание, что мы написали заглавное имя геттера NAME. Это просто рекомендуемое соглашение о именовании, и вам не обязательно следовать этому соглашению, если оно вам не нравиться

Теперь, когда мы определили метод получения имени, невероятно легко получить значение имени внутри нашего компонента. Это показано во фрагменте ниже.

let name = this.$store.getters.NAME

Мы выяснили, как получить данные из хранилища, давайте посмотрим, как мы можем установить данные в нашем хранилище. Для этого нужно задать сеттеры, верно? Да но за исключением того, что сеттеры Vuex называются немного по-другому. Сеттеры во Vuex называются мутациями (mutation).

mutations : {
  SET_NAME : (state, payload) => {
    state.name = payload,
  }
}

Что такое payload в нашем примере? payload – это просто данные, передаваемые нашей мутации от компонента, совершающего мутацию. Как это делается?

this.$store.commit(“SET_NAME”,your_name)

Этот фрагмент кода изменит состояние и назначит любое значение находящееся в переменной your_name, свойству name внутри нашего состояния Vuex.

Мутации синхронны

Представьте, что у нас есть список имен, хранящихся в базе данных на удаленном сервере. API предоставляет нам метод, который можно использоваться в нашем приложении Vue.js. Конечно, мы можем использовать Axios, чтобы сделать запрос на получение данных по API.

let { data } = await Axios.get(‘https://myapiendpoint.com/api/names')

Затем мы можем зафиксировать (commit) возвращенный массив в нашем хранилище Vuex с помощью мутации. Просто .. верно? Ну, не совсем. Мутации синхронны, и мы не можем выполнять асинхронные операции, такие как вызовы API, внутри мутации.

Так что же нам теперь делать? Мы используем Действия (Actions).

Действия похожи на мутацию, но вместо того, чтобы напрямую изменять данные в хранилище, они совершают мутацию. Странно? Давайте посмотрим на объявление действия (action).

actions : {
  SET_NAME : (context, payload){
     context.commit("SET_NAME",payload);
  }
}

Мы определили действие с именем SET_NAME, которое принимает контекст и payload в качестве параметров. Действие фиксирует (commit) созданную ранее мутацию SET_NAME с переданной ей payload, то есть your_name.

Теперь вместо того, чтобы зафиксировать (commit) мутацию напрямую, наши компоненты отправляют (dispatch) действие SET_NAME с новым именем в качестве payload следующим образом:

this.$store.dispatch("SET_NAME",your_name)

Затем действие фиксирует (commit) мутацию с передаваемой ей payload, то есть your_name.

Но почему все так сложно ?

Вам может быть интересно, зачем вообще нужны действия, когда мы можем просто зафиксировать наши мутации с новым значением непосредственно из наших компонентов. Как упоминалось выше, мутации являются синхронными, а действия – нет.

В приведенном выше примере рассмотрим случай, когда вам нужно обновить значение имени не только в хранилище, но и в базе данных, работающей на удаленном сервере. Я уверен, что именно так вы собираетесь использовать Vuex в реальном проекте в 99% случаев. Посмотрите на следующий фрагмент кода.

mutations : {
  SET_NAME : (state, name) => {
    state.name = name
  }
},
actions : {
  SET_NAME : async (context, name) => {
    let { data } = await Axios.post('http://myapiendpoint.com/api/name',{name : name})
    if(data.status == 200){
      context.dispatch('SET_NAME', name)
    }
  }
}

Сам код не требует пояснений. Мы используем Axios для POST запроса. Если POST запрос успешен, то есть значение имени поля было успешно изменено на сервере, мы фиксируем мутацию SET_NAME, чтобы обновить значение name также в нашем хранилище.

Никогда не совершайте свои Мутации (Mutations) напрямую. Всегда используйте действия (Actions), чтобы зафиксировать (commit) мутацию

Настройка Vuex хранилища в Vue.JS

Давайте продолжим и узнаем, как на самом деле мы можем использовать Vuex в реальном приложении.

Шаг 1. Инсталляция Vuex

npm install --save vuex

Шаг 2. Создание Vuex хранилища

1. Создайте папку с именем store внутри корневой папки проекта.

2. Создайте файл index.js внутри только что созданной папки. Внесите в него следующий код:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex);

export const store = new Vuex.Store({
  state: {},
  getters : {},
  mutations: {},
  actions : {}  
})

Шаг 3. Добавте Vuex хранилище в Vue.Js приложение

1. Импортируйте store внутри файла main.js.

import { store } from './store'

2. Присоедините store к экземпляру Vue, как показано ниже.

new Vue({
  el: '#app',
  store,
  router,
  render: h => h(App)
})

И это все. Теперь мы можем добавлять переменныеопределять геттеры (getters)мутации (mutations) и действия (actions) внутри Vuex хранилища.

Пример приложения TODO List

Ниже приведен пример Vuex хранилище простого приложения реализующий TODO list (список дел).

import Vue from 'vue'
import Vuex from 'vuex'
import Axios from 'axios'

Vue.use(Vuex);

export const store = new Vuex.Store({
  state: {
    todos : null
  },
  getters : {
    TODOS : state => {
      return state.todos;
    }
  },
  mutations: {
    SET_TODO : (state,payload) => {
      state.todos = payload
    },
    ADD_TODO : (state,payload) => {
      state.todos.push(payload)
    },
  },
  actions:{
   GET_TODO : async (context,payload) => {
      let { data } = await Axios.get('http://yourwebsite.com/api/todo')
      context.commit('SET_TODO',data)
   },
   SAVE_TODO : async (context,payload) => {
      let { data } = await Axios.post('http://yourwebsite.com/api/todo')
      context.commit('ADD_TODO',payload)
   },
 },
})

Добавление нового элемента в список дел

Для создания нового элемента внутри компонента мы можем использовать “действие” (action) SAVE_TODO, как указано с примере ниже:

let item = "Get groceries"
this.$store.dispatch('SAVE_TODO',item)

Действие SAVE_TODO осуществляет POST запрос к методу API и затем фиксирует мутацию ADD_TODO, которая помещает новый элемент в переменную состояния todos.

Извлечение списка to-do

Для того что бы получить список дел в компоненте используйте следующий код. Вставьте внутрь блока mounted() второе действие GET_TODO, в котором извлекаются список дел соответствующим API и затем сохраняется в соответствующей переменной todos.

mounted(){
    this.$store.dispatch('GET_TODO')
}

Получения доступ к списку дел внутри вашего компонента

Для того чтобы получить доступ к элементу todos внутри компонента, создайте вычисляемое свойство, как показано во фрагменте ниже:

computed: {
  todoList(){
     return this.$store.getters.TODOS
  }
}

Теперь внутри компонента можно получить доступ к вычисляемому свойству, как показано ниже.

<div class="todo-item" v-for="item in todoList"></div>

Использование метода Map Getters

Существует еще более простой способ доступа к списку задач внутри компонента с помощью метода mapGetters:

import {mapGetters} from 'vuex
computed: {
...mapGetters(['TODOS']),
  //Other computed properties
}

Возможно, вы уже догадались, что код внутри шаблона должен быть изменен, как показано ниже:

<div class="todo-item" v-for="item in TODOS"></div>

Обратите внимание, как мы использовали оператор ES6 spread [ … ] внутри вычисляемых свойств.

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

Это утверждение нуждается в небольшом объяснении. Мы уже научились определять действия для получения и установки элемента todos в нашем состоянии. Что если нам нужно обновить элемент и отметить его как завершенный? Где нам запускать код для этого?

В Интернете вы можете найти противоречивые мнения по этому вопросу, и даже в документации нет четких указаний на этот счет.

Я бы рекомендовал хранить все ваши вызовы к API внутри Действие (Actions) в Vuex. Таким образом, каждое изменение, связанное с обновлением состоянием, может поступить только из хранилища и таким образом это облегчит отладку и понимание кода. Это также имеет дополнительное преимущество, заключающееся в упрощении редактирования кода.

Организция кода

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

Создайте новую папку внутри вашего хранилища с именем modules. Добавьте новый файл todos.js внутрь папки modules со следующим содержимым.

const state = {}
const getters = {}
const mutations = {}
const actions = {}
export default{
    state, getters, mutations, actions
} 

Теперь переместите переменные состояния, геттеры, мутации и действия из index.js в файл todos.js. Не забудьте импортировать Axios. Теперь все, что нам нужно сделать, это сообщить Vuex, что мы создали модуль хранилища и место где его найти. Наш новый файл index.js должен выглядеть примерно так.

import Vue from 'vue'
import Vuex from 'vuex'
import Axios from 'axios'
import todos from './modules/todos'

Vue.use(Vuex);

export const store = new Vuex.Store({
  
  state: {},
  getters : {},
  mutations: {},
  actions:{},
  
  modules : {
    todos
  }
})

А файл todos.js теперь должен быть таким образом:

import Axios from 'axios'

state = {
    todos : null
},
getters = {
   TODOS : state => {
     return state.todos;
   }
},
mutations = {
  SET_TODO : (state,payload) => {
     state.todos = payload
  },
  ADD_TODO : (state,payload) => {
     state.todos.push(payload)
  },
},
actions = {
  GET_TODO : async (context,payload) => {
      let { data } = await Axios.get('http://yourwebsite.com/api/todo')
      context.commit('SET_TODO',data)
  },
  SAVE_TODO : async (context,payload) => {
      let { data } = await Axios.post('http://yourwebsite.com/api/todo')
      context.commit('ADD_TODO',payload)
  }
}

export default {
  state,getters,mutations,actions
}

Важные выводы

  1. Состояние приложения хранится в виде одного большого объекта JSON.
  2. Геттеры (Getters) используются для получение значений из хранилища.
  3. Мутации (Mutations) обновляют состояния хранилища. Важно помнить что мутации синхронны.
  4. Все асинхронные операции должны выполняться внутри Действий (Actions). Действия изменяют состояние, совершая мутацию.
  5. Всегда совершайте Мутацию (Mutation) только через Действие (Action).
  6. Используйте модули для организации хранилища в виде несколько небольших файлов.

Vuex делает работу с Vue намного проще и веселее. Если вы новичок, возможно вы чаще будете задаваться вопросом, нужно ли вам использовать Vuex или нет. Следуй своей интуиции. Довольно быстро вы научитесь отвечать на этот вопрос самостоятельно.

Оригинал

Была ли вам полезна эта статья?
[15 / 4.2]

Spread the love
Editorial Team

View Comments

  • Лучшее объяснение про vuex что я видел. Смотрел много примеров на youtube, читал оф. документацию, но нигде так понятно не расписано про все сразу, как здесь. Спасибо.

  • actions : {
    SET_NAME : async (context, name) => {
    let { data } = await Axios.post('http://myapiendpoint.com/api/name',{name : name})
    if(data.status == 200){
    context.dispatch('SET_NAME', name)
    }
    }
    Здесь разве не context.commit('SET_NAME', name) должен быть?

    • На пару параграфов выше написано, что вместо commit здесь так же можно использовать dispatch.
      "Теперь вместо того, чтобы зафиксировать (commit) мутацию напрямую, наши компоненты отправляют (dispatch) действие SET_NAME с новым именем в качестве payload следующим образом:..."

      • Да, но там вызов action из action, а не из компонента... Этакая загадочная рекурсия. Этот код точно работает? И я уже второй, кто на это обратил внимание. Как минимум этот момент требует объяснения... А вообще материал подан не плохо.

  • Спасибо, отличный материал. Кратко и понятно.

  • Получается, что данные хранятся в двух местах: на сервере и в массиве state.todos.
    Хорошо ли это?
    А если todos станет очень много?

  • Сколько можно смотреть на эти всплывающие окна? Они сбивают с мысли, отвлекают от чтения. На каждом сайте...

    Если бы я действительно хочу что-то толковое сказать, то неужели не скажу? Или меня считают такой бестолчью, которая не найдет поле для комментария?

    Наверное, вам лишь бы комментарий. Ну так вот вам и комментарий, как просили

  • Еще и $0.remove() не получимлось воспользоваться((((((((((((

  • добрый день, у меня маин выглядит вот так, при добавлении этого new Vue({ el: ‘#app’, store, router, render: h => h(App)}) код не работает, не понимаю что не так

Recent Posts

Vue 3.4 Новая механика v-model компонента

Краткий перевод: https://vuejs.org/guide/components/v-model.html Основное использование​ v-model используется для реализации двусторонней привязки в компоненте. Начиная с Vue…

4 месяца ago

Анонс Vue 3.4

Сегодня мы рады объявить о выпуске Vue 3.4 «🏀 Slam Dunk»! Этот выпуск включает в…

4 месяца ago

Как принудительно пере-отобразить (re-render) компонент Vue

Vue.js — это универсальный и адаптируемый фреймворк. Благодаря своей отличительной архитектуре и системе реактивности Vue…

1 год ago

Проблемы с установкой сертификата на nginix

Недавно, у меня истек сертификат и пришлось заказывать новый и затем устанавливать на хостинг с…

1 год ago

Введение в JavaScript Temporal API

Каким бы ни было ваше мнение о JavaScript, но всем известно, что работа с датами…

1 год ago

Когда и как выбирать между медиа запросами и контейнерными запросами

Все, кто следит за последними событиями в мире адаптивного дизайна, согласятся, что введение контейнерных запросов…

1 год ago