Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Vue
Code

Создание сложных и крупномасштабных приложений на Vue.js с помощью Vuex

by
Difficulty:AdvancedLength:LongLanguages:

Russian (Pусский) translation by Alexey Pyltsyn (you can also view the original English article)

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

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

Что такое Vuex?

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

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

Зачем нужен Vuex?

Вы можете спросить: зачем мне нужен Vuex? Разве я не могу просто поместить разделяемое состояние в обычный файл JavaScript и импортировать его в свое приложение Vue.js?

Конечно, вы можете, так сделать, но по сравнению с простым глобальным объектом, хранилище Vuex имеет ряд существенных преимуществ и выгод:

  • Хранилище Vuex — реактивное. Как только компоненты получают из него состояние, они будут реактивно обновлять свои представления каждый раз, когда состояние изменяется.
  • Компоненты не могут напрямую изменять состояние хранилища. Единственный способ изменить состояние хранилища — явно зафиксировать мутации. Это гарантирует, что каждое изменение состояния оставляет отслеживаемую запись, что облегчает отладку и тестирование приложения.
  • Вы можете легко отлаживать приложение благодаря интеграции Vuex с расширением Vue DevTools.
  • Хранилище Vuex дает вам общую картину состояния, как все связано и влияет на приложение.
  • Проще поддерживать и синхронизировать состояние между несколькими компонентами, даже если иерархия компонентов изменяется.
  • Vuex делает возможным непосредственное взаимодействие компонентов друг с другом.
  • Если компонент уничтожен, состояние в хранилище Vuex останется нетронутым.

Начало работы с Vuex

Прежде чем мы начнем, я хочу кое-что прояснить.

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

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

Наконец, я буду использовать синтаксис ES2015. Если вы не знакомы с этим, вы можете узнать про него здесь.

А теперь давайте начнем!

Настройка проекта Vuex

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

Я добавил немного кода CSS, чтобы компоненты выглядели красивее, но вам не нужно беспокоиться об этом коде. Это только поможет вам получить визуальное представление о том, что происходит. Просто скопируйте и вставьте в тег <head> следующее:

Теперь давайте создадим несколько компонентов для работы. Внутри тега <script>, прямо перед закрывающим </body>, поместите следующий код на Vue:, прямо над закрывающим тегом , поместите следующий код Vue:

Теперь у нас есть экземпляр Vue, родительский компонент и два дочерних компонента. У каждого компонента есть заголовок «Score:», в который мы выводим состояние приложения.

Последнее, что вам нужно сделать, это поместить элемент <div> с атрибутом с id="app" сразу после открытия <body>, а затем поместите родительский компонент внутри него:

Подготовительная работа завершена, и мы готовы двигаться дальше.

Изучение Vuex

Управление состоянием

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

Состояние и мутации являются основой для любого хранилища Vuex:

  • state — это объект, который содержит состояние данных приложения.
  • mutations — это также объект, содержащий методы, которые влияют на состояние.

Геттеры и действия похожи на логические демонстрации состояния и мутаций:

  • getters содержат методы, используемые для абстрагирования доступа к состоянию и выполнения некоторых задач предварительной обработки, если это необходимо (вычисление данных, фильтрация и т.д.).
  • actions — это методы, используемые для запуска мутаций и выполнения асинхронного кода.

Давайте рассмотрим следующую диаграмму, чтобы немного разобраться:

Vuex State Management Workflow Diagram

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

Чтобы изменить состояние, конкретный компонент Vue должен зафиксировать мутации (например, this.$store.commit('increment', 3)), а затем эти мутации изменят состояние (score вычисляется в 3). После этого геттеры автоматически обновляются благодаря реактивной системе Vue, и они отображают обновления в виде компонента (с помощью this.$store.getters.score).

Мутации не могут выполнять асинхронный код, потому что это сделает невозможным запись и отслеживание изменений в инструментах отладки, таких как Vue DevTools. Чтобы использовать асинхронную логику, вы должны поместить ее в действия. В этом случае компонент сначала будет отправлять действия (this.$store.dispatch('incrementScore', 3000)), где выполняется асинхронный код, а затем эти действия будут совершать мутации, которые изменят состояние.

Создание заготовки для хранилища Vuex

Теперь, когда мы изучили, как работает Vuex, давайте создадим заготовку для нашего хранилища Vuex. Поместите следующий код перед регистрацией компонента ChildB:

Для обеспечения глобального доступа к хранилищу Vuex из каждого компонента, нам нужно добавить свойство store в экземпляр Vue:

Теперь мы можем получить доступ к хранилищу из каждого компонента с помощью переменной this.$store.

Пока что, если вы откроете проект на CodePen в браузере, вы увидите следующий результат.

App Skeleton

Свойства состояния

Объект состояния содержит все общие данные в вашем приложении. Конечно, при необходимости каждый компонент может иметь свое собственное приватное состояние.

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

Теперь вы можете непосредственно получить доступ к состоянию со счетом. Давайте вернемся к компонентам и повторно используем данные из хранилища. Чтобы иметь возможность повторно использовать реактивные данные из состояния хранилища, вам нужно использовать вычисляемые свойства. Итак, давайте создадим вычисляемое свойство score() в родительском компоненте:

В шаблоне родительского компонента поместите выражение {{ score }}:

А теперь сделайте то же самое для двух дочерних компонентов.

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

Создание геттеров

Конечно, хорошо, что вы можете повторно использовать ключевое слово this.$store.state внутри компонентов, как вы видели выше. Но представьте себе следующие сценарии:

  1. В крупномасштабном приложении, где несколько компонентов получают доступ к состоянию хранилища с помощью this.$store.state.score, вы решаете изменить имя score. Это означает, что вы должны изменить имя переменной внутри каждого компонента, который ее использует!
  2. Вы хотите использовать вычисляемое значение состояния. Например, допустим, вы хотите дать игрокам бонус в 10 баллов, когда счет достигнет 100 баллов. Таким образом, когда счет достигает 100 баллов, добавляется 10 бонусных очков. Это означает, что каждый компонент должен содержать функцию, которая повторно использует счет и увеличивает его на 10. У вас будет повторный код в каждом компоненте, что совсем не хорошо!

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

Давайте создадим геттер score():

Геттер получает state в качестве первого аргумента, а затем использует его для доступа к свойствам состояния.

Примечание: геттеры также получают getters в качестве второго аргумента. Вы можете использовать его для доступа к другим геттерам в хранилище.

Во всех компонентах измените вычисляемое свойство score(), чтобы использовать геттер score() вместо состояния со счетом напрямую.

Теперь, если вы решите изменить score на result, вам нужно обновить его только в одном месте: в геттере score(). Посмотрите результат на CodePen!

Создание мутаций

Мутации являются единственным допустимым способом изменения состояния. Запуск изменений просто означает фиксацию мутаций в методах компонента.

Мутация — это в значительной степени функция-обработчик события, которая определяется по имени. Функции обработчика мутаций получают state в качестве первого аргумента. Вы также можете передать дополнительный второй аргумент, который называется payload для мутации.

Давайте создадим мутацию increment():

Мутации не могут вызываться напрямую! Чтобы выполнить мутацию, вы должны вызвать метод commit() с именем соответствующей мутации и возможными дополнительными параметрами. Это может быть только один параметр, например, step в нашем случае, или может быть несколько обернутых параметров в объект.

Давайте использовать мутацию increment() в двух дочерних компонентах путем создания метода changeScore():

Мы фиксируем мутацию вместо непосредственного изменения this.$store.state.score, потому что мы хотим явно отслеживать изменения, сделанные мутацией. Таким образом, мы создаем логику нашего приложения более прозрачной, отслеживаемой и простой для понимания. Кроме того, это позволяет реализовать инструменты, такие как Vue DevTools или Vuetron, которые могут регистрировать все мутации, делать снимки состояния и выполнять отладку во времени.

Теперь давайте начнем использовать метод changeScore(). В каждом шаблоне двух дочерних компонентов создайте кнопку и добавьте в нее обработчик события click:

Когда вы нажимаете кнопку, состояние увеличивается на 3, и это изменение будет отражаться во всех компонентах. Теперь мы фактически достигли прямого межкомпонентного взаимодействия, что невозможно с помощью встроенного в Vue.js механизма «передачей входных параметров вниз, а событий вверх». Посмотрите это в нашем примере CodePen.

Создания действий

Действие — это просто функция, которая фиксирует мутацию. Это косвенно изменяет состояние, что позволяет выполнять асинхронные операции.

Давайте создадим действие incrementScore():

Действия получают context в качестве первого параметра, который содержит все методы и свойства из хранилища. Обычно мы просто извлекаем нужные нам части, используя деструктуризацию аргументов ES2015. Метод commit нам нужен очень часто. Действия также получают второй аргумент payload, как и мутации.

В компоненте ChildB измените метод changeScore():

Чтобы вызвать действие, мы используем метод dispatch() с именем соответствующего действия и дополнительными параметрами, как и с мутациями.

Теперь кнопка Change Score из компонента ChildA увеличит счет на 3. Аналогичная кнопка из компонента ChildB сделает то же самое, но с задержкой в 3 секунды. В первом случае мы выполняем синхронный код и используем мутацию, но во втором случае мы выполняем асинхронный код, и вместо этого нам нужно использовать действие. Посмотрите, как все это работает в нашем примере CodePen.

Вспомогательные методы сопоставления Vuex

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

Вместо написания вычисляемоего свойства score() следующим образом:

Мы просто используем вспомогательный метод mapState() следующим образом:

И свойство score() создается автоматически для нас.

То же самое верно для геттеров, мутаций и действий.

Для создания получателя score() мы используем вспомогательный метод mapGetters():

Чтобы создать метод changeScore(), мы используем вспомогательный метод mapMutations() следующим образом:

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

Если мы хотим, чтобы changeScore() использовал действие вместо мутации, мы используем mapActions() следующим образом:

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

Примечание: все вспомогательные методы по сопоставлению возвращают объект. Итак, если мы хотим использовать их в сочетании с другими локальными вычисляемыми свойствами или методами, нам нужно объединить их в один объект. К счастью, с помощью оператора расширения объекта (...) мы можем сделать это без использования какого-либо утилиты.

В нашем CodePen вы можете увидеть пример того, как все вспомогательные методы сопоставления используются на практике.

Создание более модульным хранилище

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

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

Использование модулей Vuex

Vuex позволяет разделить объект хранилища на отдельные модули. Каждый модуль может содержать свое собственное состояние, мутации, действия, геттеры и другие вложенные модули. После того, как мы создали необходимые модули, мы регистрируем их в хранилище.

Давайте посмотрим на это в действии:

В приведенном выше примере мы создали два модуля, по одному для каждого дочернего компонента. Модули — это всего лишь простые объекты, которые мы регистрируем как scoreBoard и resultBoard в объекте modules внутри хранилища. Код для childA такой же, как и в хранилище из предыдущих примеров. В коде для childB мы добавляем некоторые изменения в значения и имена.

Давайте теперь улучшим компонент ChildB, чтобы отразить изменения в модуле resultBoard.

В компоненте ChildA единственное, что нам нужно изменить, — это метод changeScore():

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

Модули с пространством имен

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

Для создания пространства имен модулям Vuex вы просто устанавливаете для свойства namespaced значение true.

В приведенном выше примере мы сделали имена свойств и методов одинаковыми для двух модулей. И теперь мы можем использовать свойство или метод с префиксом имени модуля. Например, если мы хотим использовать метод получения score() из модуля resultBoard, мы набираем его так: resultBoard/score. Если мы хотим получить score() из модуля scoreBoard, мы набираем его так: scoreBoard/score.

Давайте теперь изменим наши компоненты, чтобы отразить сделанные нами изменения.

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

Разделение хранилища Vuex на отдельные файлы

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

Поэтому следующий логический шаг — разделить хранилище Vuex на отдельные файлы. Идея состоит в том, чтобы иметь отдельный файл для самого хранилища и один для каждого из его объектов, включая модули. Это означает наличие отдельных файлов для состояния, геттеров, мутаций, действий и для каждого отдельного модуля (store.js, state.js, getters.js и т.д.). Пример этой структуры можно увидеть в конце следующего раздела.

Использование однофайловых компонентов Vue

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

Итак, в нашем случае у нас будет три файла: Parent.vue, ChildA.vue и ChildB.vue.

Наконец, если мы объединим все три метода, мы получим следующую или похожую структуру:

В репозитории на GitHub этой статьи вы можете увидеть готовый проект с приведенной выше структурой.

Вывод

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

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

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

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

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

Заключение

Ну вот! Вы уже знаете основные концепции Vuex и готовы начать применять их на практике.

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

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.