Всем привет! Нам бы хотелось представить вашему вниманию статью Энтони Гора о миграции Vue.js-приложения на Vuex.


Вид приложения, над которым будет вестись работа по миграции

Далее следует перевод статьи. Всех, кому интересна данная тема, приглашаю под кат.

Одной из сложностей в освоении Vuex является то, что это не столько библиотека, сколько паттерн проектирования. Из этого следует, что работа с Vuex – не столько работа с API, сколько структурирование вашего кода в соответствии с паттерном. Если вы новичок во Vuex, это может отпугивать.

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

Если вы не уверены, следует ли вам использовать Vuex, я рекомендую вам сначала прочесть вот эту статью: “Что за Vuex: Руководство для новичков по хранилищу данных во Vue

Учебный пример: Vue.js-кинотеатр


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



Если хотите узнать, как построить Vue.js-кинотеатр с нуля, это часть моего полного курса разработки на Vue.js.
Помните, что целью Vuex является управление состоянием приложения. Из этого следует, что для того, чтобы мигрировать Vue.js-приложение на Vuex, мы должны определить, каково будет его состояние. Мы скоро увидим состояние в коде, но полезно сначала осознать, каким оно будет, посредством простого изучения того, что вообще делает приложение, например, в нашем случае, оно отображает список фильмов, который может быть отфильтрован по дню, времени сеанса или жанру.

Какие данные приложения должны принадлежать Vuex?


Используя Vue Devtools, или же просто инспектор кода, мы можем увидеть данные принадлежащие каждому vue-компоненту:



Наша задача не переносить все данные во Vuex-стор (центральное хранилище данных). Вместо этого мы хотим определить данные, которые:

  1. Изменяются на протяжении жизненного цикла приложения (статические данные не требуют особого менеджмента).
  2. Разделены между несколькими объектами/компонентами.

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

Например, взгляните на эту группу компонентов check-filter, кастомных чекбоксов для фильтрации фильмов:



Каждый check-filter компонент имеет свойство checked, а также текст, который передаётся ему в качестве свойства, в частности Before 6pm, After 6pm и т.д.:

src/components/CheckFilter.vue

<template>...</template>
<script>
  export default {
    data() {
      return {
        checked: false
      }
    },
    props: [ 'title' ],
    ...
  }
</script>

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

Свойство title, с другой стороны, не изменяется и никак не влияет на другие компоненты. Поэтому нам нет нужды управлять ими с помощью Vuex-стора.

Примечание: если у вас есть какое-либо локальное состояние, которое вы хотели бы отслеживать с помощью Vue DevTools, тогда это нормально, если вы добавите его в стор; это не является нарушением паттерна.

Свойcтва > Хранилище (Стор)


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

К примеру, я передаю свойство day из корневого компонента приложения в day-select и movie-list компоненты. C помощью компонента day-select пользователь выбирает день, для которого он хочет посмотреть фильмы, и список фильмов фильтруется в соответствии с выбранным значением:


Компонент day-select

Чтобы переместить day во Vuex мы можем просто удалить его из корневого компонента и переместить в объект state хранилища Vuex:

export default new Vuex.store({
  state: {
    day: moment() // the initial value
  },
  ...
});

Теперь мы можем удалить привязку из темплейта v-bind:day="day", а в компоненте заменить стандартное свойство на computed-свойство, которое обращается к нашему хранилищу (стору). В случае с компонентом day-select это будет следующий код:

export default {
  props: [ 'day' ],
  ...
}

который преобразуется в

export default {
  computed: {
    day() {
      return this.$store.state.day
    }
  }
}

События > Мутации


Тогда как свойства – это данные, которые передаются в компонент, то события влияют на состояние этих данных. Слушатели событий в вашем коде должны быть преобразованы во Vuex-мутации. Функции, вызывающие события, будут переделаны, и станут вызывать мутации, а не события.

Вернемся к нашему примеру с компонентом day-select. Когда пользователь изменит день, кликнув на соответствующий элемент интерфейса, вызывается следующий метод компонента:

selectDay(day) {
  this.$emit('setDay', day);
}

Корневой компонент содержит слушатель события, который будет реагировать на кастомное событие:

created() {
  this.$on('setDay', day => { this.day = day; });
}

Теперь функция-слушатель может быть перенесена из корневого компонента в объект мутаций нашего хранилища с небольшими изменениями:

src/store/index.js
export default new Vuex.Store({
  state: {
    day: moment()
  },
  mutations: {
    setDay(state, day) {
      state.day = day;
    }
  }

Далее нужно заменить вызов события в компоненте на вызов мутации нашего хранилища:

selectDay(day) {
  this.$store.commit('setDay', day);
}

Наконец, мы также можем удалить слушатель события из темплейта, т.е. код v-on:click="setDay".

AJAX > Actions (действия)


Действия во Vuex – это механизм обработки ассинхронных мутаций. Если вы используете AJAX, чтобы обновлять состояние своего приложения, возможно вам потребуется обернуть его в действие (action).

Во Vue.js-кинотеатре я использую AJAX (с помощью Vue Resource HTTP client), чтобы получать данные из API моего сервера:

src/main.js

created() {
  this.$http.get('/api').then(response => {
    this.movies = response.data;
  }); 
}

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

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

src/store/index.js

export default new Vuex.Store({
  state: {
    ...
    movies: []
  },
  mutations: {
    ...
    setMovies(state, movies) {
      state.movies = movies;
    }
  },
  actions: {
    getMovies({ commit }) {
      Vue.http.get('/api').then(response => {
        commit('setMovies', response.data);
      });
    }
  }
});

Метод created теперь должен только лишь вызывать действие:

src/main.js
created() {
  this.$store.dispatch('getMovies');
}

Результаты и преимущества


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

Отладка


Теперь, когда наше приложение управляется через Vuex, мы легко можем увидеть любые изменения в нем с помощью Vue Devtools:



Vuex и Vue Devtools не только логируют изменения в хранилище, но и позволяют вам «отмотать» изменения, так что вы можете пошагово проследить, каким образом они воздействуют на приложение.

Как сказал создатель библиотеки Эван Вью: «Преимущество Vuex в том, что изменения в нем являются отслеживаемыми, воспроизводимыми и восстанавливаемыми».

Разделение компонентов и состояния


Во Vue.js-кинотеатре мы отслеживаем фильтры в двух массивах, один из которых содержит данные о времени сеанса, другой – жанр фильма. Добавление и удаление фильтров ранее обрабатывалось функцией в корневом компоненте. Используя Vuex мы можем перенести эту логику в мутацию в нашем хранилище:

src/store/index.js

mutations: {
    ...
    checkFilter(state, { category, title, checked }) {
      if (checked) {
        state[category].push(title);
      } else {
        let index = state[category].indexOf(title);
        if (index > -1) {
          state[category].splice(index, 1);
        }
      }
    }
  },

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

Сокращение компонент


Без такого паттерна хранилища как Vuex мы передаем данные компонентам в виде свойств и событий, которые должны быть объявлены в HTML-шаблоне. В больших приложениях из-за этого шаблоны могут становится достаточно многословными.

Управление данными во Vuex позволяет этот код:

<movie-list
  :genre="genre" 
  :time="time" 
  :movies="movies" 
  :day="day"
></movie-list>

преобразовать в этот:

<movie-list></movie-list>

Понравилась статья? Подпишитесь на рассылку и получайте самые последние статьи по Vue.js на почту.

Комментарии (10)


  1. Desprit
    27.08.2017 16:22

    Если хочется с самого начала получить легко масштабируемую структуру приложения, использующего Vuex, то у них в репозитории отличный пример:
    github.com/vuejs/vuex/tree/dev/examples/shopping-cart


  1. r-moiseev
    27.08.2017 18:03
    +1

    Все эти примеры слишком простые и плоские. Меня вот интересует что делать с вложенными данными. Разделение на модули приводит к странному, ибо не возможно из одного модуля мутировать стейт другого модуля. Плоская структура же ведет к лапшевидному коду.


    1. Desprit
      27.08.2017 20:01

      ибо не возможно из одного модуля мутировать стейт другого модуля

      Это точно так? State можно получить из другого модуля:
      someAction ({commit, state, rootState}) {
      console.log(rootState.moduleName.stateField)
      }

      Я думал, что и диспатчить можно тогда…


      1. r-moiseev
        27.08.2017 21:09
        -1

        Стейт модуля получить можно, но писать в стейт напрямую это плохо. Мутацию модуля дернуть никак.


        1. Desprit
          27.08.2017 21:42

          1. r-moiseev
            27.08.2017 22:52

            Экшны! Приходится плодить экшны на самом деле являющиеся всего лишь обертками миграций


            1. Desprit
              28.08.2017 11:40

              Да, вы правы, я не обратил внимания, что вы говорите именно о мутациях.


    1. AnneSmith
      27.08.2017 21:08

      все что тут написано — это совершенно ненужное усложнение искусственно созданных объектов, которых не существует в интерфейсе
      если вы создадите только один объект — компонент интерфейса с разными типами input, checkbox, select, fieldset итд, вы сможете связать эти компоненты в единую структуру с единым корнем в обычном json
      в этом случае вам не нужны искусственно навязываемые состояния искусственных объектов, вы всегда можете прочесть текущее значение любого объекта интерфейса и привязать к нему логику: по факту в интерфейсе есть только два действия — показать/скрыть объект, изменить значение объекта
      таким образом можно легко управлять огромными приложениями, с клонируемыми частями, а объекты интерфейса остаются объектами интерфейса в любом приложении, разница состоит только в html шаблонах, которые легко заменить
      в итоге вы работаете только с компонентами интерфейса на обычном javascript & json и вообще не тратите свое время на html, и все это легко переносится куда угодно с минимальными модификациями
      время разработки любого сложного приложения в таком варианте сводится к минимуму


  1. aliev
    28.08.2017 03:08

    Про Vuex неплохо написано в документации.
    И если человек по каким-то причинам не использовал Vuex в своем проекте, скорее всего он ему не нужен. Потому как это нечто вроде сервисов в ангуляре, де факто необходимая штука.


  1. majorius
    28.08.2017 09:25

    К сожалению в статье ни слова про mapState, mapActions и mapGetters, в то время как в некоторых случаях это позволяет сократить количество кода на порядок.