Pull to refresh

Книга «Vue.js в действии»

Reading time14 min
Views21K
imageПривет, Хаброжители! Цель этой книги — дать вам знания, с помощью которых вы без колебаний присоединитесь к любому проекту, использующему эту библиотеку. Книга предназначена для всех, кто заинтересован в изучении Vue.js и имеет опыт работы с JavaScript, HTML и CSS. От вас не требуются глубокие знания этой области, но понимание основ, таких как массивы, переменные, циклы и HTML-элементы, не помешает.

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

10.1. Для чего нам Vuex


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

Если у вас уже есть опыт работы с другими фреймворками для создания одностраничных приложений, некоторые из этих концепций могут показаться знакомыми. Например, React использует похожую систему управления состоянием под названием Redux. Vuex и Redux созданы под влиянием проекта Flux. Это архитектура, предложенная компанией Facebook, которая призвана упростить построение клиентских веб-приложений. Она способствует движению данных в одном направлении: от действий к диспетчеру, затем к хранилищу и в конце — к представлению. Такой подход позволяет отделить состояние от остальной части приложения и поощряет синхронные обновления. Больше о Flux можно узнать из официальной документации на странице facebook.github.io/flux/docs/overview.html.

Vuex работает по тому же принципу, помогая изменять состояние предсказуемо и синхронно. Разработчикам не нужно беспокоиться о последствиях обновления состояния синхронными и асинхронными функциями. Представьте, что мы взаимодействуем с серверным API, который возвращает данные в формате JSON. Что произойдет, если в тот же момент эти данные будут модифицированы сторонней библиотекой? Мы не хотим получить непредсказуемый результат. Vuex помогает избежать подобных ситуаций, исключая любые асинхронные изменения.
Вам, наверное, интересно, зачем нам вообще нужна библиотека Vuex. В конце концов, Vue.js позволяет передавать информацию в компоненты. Как вы знаете из предыдущих глав, для этого предназначены входные параметры и пользовательские события. Мы даже могли бы создать собственную шину событий для передачи данных и межкомпонентного взаимодействия. Пример такого механизма представлен на рис. 10.1.

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

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

Посмотрим, как это реализуется с помощью Vuex. На рис. 10.2 видно, что компонент EditBio является дочерним по отношению к панели администратора. Ему нужен доступ к информации о пользователе, чтобы он мог ее обновить. Работая с Vuex, мы можем обратиться к центральному хранилищу, изменить данные и сохранить изменения непосредственно из компонента EditBio. Это намного лучше, чем передавать информацию из корневого экземпляра Vue.js в компонент Admin, а затем в EditBio с помощью входных параметров. Нам было бы сложно уследить за данными, размещенными в разных местах.

image

image

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

10.2. Состояние и мутации в Vuex


Vuex хранит состояние всего приложения в едином объекте, который еще называют единым источником истины. Как можно догадаться из названия, все данные собраны в одном месте и не дублируются на других участках кода.
ПОДСКАЗКА

Стоит отметить, что мы не обязаны хранить все свои данные в Vuex. Отдельные компоненты могут иметь собственное локальное состояние. В определенных ситуациях это оказывается предпочтительно. Например, у вашего компонента есть переменная, которая используется только внутри него. Она должна оставаться локальной.
Рассмотрим простой пример работы с состоянием в Vuex. Весь наш код будет размещен в одном файле. Позже вы узнаете, как добавить Vuex в проект с помощью Vue-CLI. Откройте текстовый редактор и создайте файл vuex-state.html. Мы будем выводить сообщение, находящееся в центральном хранилище, и счетчик. Итог показан на рис. 10.3.

image Сначала добавим теги script со ссылками на Vue и Vuex. Затем создадим HTML-разметку. Мы будем задействовать теги H1, H2, H3 и button. Тег h1 отображает заголовок с локальной переменной, объявленной в экземпляре Vue.js. Сообщения welcome и counter выполняются в виде вычисляемых свойств на основе хранилища Vuex.

Элемент button инициирует действие increment. Скопируйте код из листинга 10.1 в файл vuex-state.html.

image

Закончив с HTML-разметкой, приступим к созданию хранилища Vuex. В нем будут находиться все данные приложения, включая свойства msg и count.

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

Создадим внутри объекта mutations функцию increment, инкрементирующую состояние. Возьмите код, представленный в листинге 10.2, и вставьте его внизу файла vuex-state.html.

image

Итак, мы подготовили HTML-разметку и хранилище Vuex. Теперь можно добавить логику, которая их свяжет. Мы хотим, чтобы шаблон отображал значения msg и counter, которые являются частью состояния Vuex. Кроме того, counter нужно обновлять.

Создайте экземпляр Vue.js с новой функцией данных, которая будет возвращать локальное свойство header с текстом Vuex App. В разделе computed добавим вычисляемые свойства welcome и counter. Первое будет возвращать store.state.msg, а второе — store.state.count.

Напоследок необходимо добавить метод под названием increment. В хранилище Vuex объявлена мутация, но мы не можем использовать ее напрямую для обновления состояния. Для этого предусмотрена специальная функция commit. Она сообщает Vuex о необходимости обновить хранилище и тем самым сохраняет изменения. Выражение store.commit('increment') выполняет мутацию. Вставьте следующий фрагмент (листинг 10.3) сразу за кодом, созданным в листинге 10.2.

image

Это уже полноценное рабочее приложение на основе Vuex! Попробуйте нажимать кнопку — при каждом нажатии счетчик должен увеличиваться на 1.

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

Скопируйте содержимое vuex-state.html в новый файл vuex-state-pass.html. На примере этого приложения покажем, как передаются аргументы.

Как видно в листинге 10.4, нужно обновить только объект mutations и метод increment. Добавьте к мутации increment еще один аргумент под названием payload. Это значение, на которое будет увеличиваться state.count. Найдите внутри метода increment вызов store.commit и укажите 10 в качестве второго аргумента. Обновите файл vuex-state.html, как показано далее.

image

Сохраните файл vuex-state-pass.html и откройте его в браузере. Теперь при нажатии кнопки счетчик должен увеличиваться на 10, а не на 1. Если что-то пошло не так, проверьте консоль браузера и убедитесь в том, что не допустили никаких опечаток.

10.3. Геттеры и действия


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

Геттеры — часть Vuex. Они позволяют реализовать унифицированный доступ к состоянию во всех компонентах. Возьмем пример из раздела 10.2 и вместо прямого доступа к хранилищу через вычисляемые свойства воспользуемся геттерами. Кроме того, сделаем так, чтобы геттер для свойства msg переводил все его буквы в верхний регистр.

Скопируйте содержимое файла vuex-state-pass.html в vuex-state-getter-action.html. Чтобы упростить задачу, оставим HTML-код неизменным. В конце должно получиться нечто похожее на рис. 10.4.

image Как видите, сообщение Hello World теперь выводится прописью. Нажатие кнопки Press Me инкрементирует счетчик так же, как и в предыдущем примере.

Найдите внутри нового файла vuex-state-getter-action.html конструкцию Vuex.Store сразу под тегом image Добавьте после mutations новый объект под названием getters. Создайте внутри этого объекта методы msg и count, как показано в листинге 10.5. Оба метода принимают один и тот же аргумент state.

Геттер msg будет возвращать state.msg.toUppercase(). Благодаря этому сообщение всегда выводится в верхнем регистре. В геттере count мы вернем state.count. После добавления геттеров снизу от мутаций файл vuex-state-getter-action.html должен выглядеть следующим образом.

image

Действия — еще одна неотъемлемая часть Vuex. Ранее я упоминал о том, что мутации должны быть синхронными. Но что, если мы работаем с асинхронным кодом? Как добиться того, чтобы асинхронные вызовы могли изменять состояние? В этом нам помогут действия Vuex.
Представьте, что приложение обращается к серверу и ожидает ответа. Это пример асинхронного действия. К сожалению, мутации асинхронны, поэтому мы не можем их здесь использовать. Вместо этого добавим асинхронную операцию на основе действия Vuex.

Для создания задержки задействуем функцию setTimeout. Откройте файл vuex-state-getter-action.html и добавьте в него объект actions сразу после getters. Внутри этого объекта разместим действие increment, которое принимает аргументы context и payload. С помощью context будем сохранять изменения. Поместим операцию context.commit внутрь setTimeout. Таким образом мы симулируем задержку на сервере. Можем также передать в context.commit аргумент payload, который затем попадет в мутацию. Обновите код на основе листинга 10.6.

После обновления Vuex.Store можно переходить к корневому экземпляру Vue.js. Вычисляемое свойство будет обращаться к хранилищу не напрямую, как раньше, а с помощью геттеров. Мы также модифицируем метод increment. Для доступа к новому свойству Vuex, которое мы создали ранее, он будет использовать вызов store.dispatch('increment', 10).

image

Первым аргументом вызова dispatch служит название действия, а во втором аргументе всегда содержатся дополнительные данные, которые этому действию передаются.
СОВЕТ

Дополнительные данные могут представлять собой обычную переменную или даже объект.
Обновите экземпляр Vue.js в файле vuex-state-getter-action.html так, как показано в листинге 10.7.
image

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

10.4. Использование Vuex в проекте зоомагазина с помощью Vue-CLI


Вернемся к проекту зоомагазина, над которым работали. Если вы не забыли, мы остановились на добавлении анимации и переходов. Теперь интегрируем библиотеку Vuex, с которой познакомились ранее.

Перенесем данные о товарах в хранилище. Как вы помните по предыдущим главам, данные инициализировались в хуке created внутри компонента Main. Теперь этот хук должен генерировать новое событие, которое инициализирует хранилище Vuex. Мы также добавим вычисляемое свойство products, которое извлекает товары с помощью геттера (его создадим позже). Итоговый результат будет выглядеть как на рис. 10.5.

image

10.4.1. Установка Vuex с помощью Vue-CLI


Для начала установим Vuex! Это простой процесс. Подготовьте последнюю версию зоомагазина, над которой мы работали в главе 8. Вы можете также загрузить весь код для этой главы на GitHub по адресу github.com/ErikCH/VuejsInActionCode.

Откройте окно терминала и перейдите в корневой каталог проекта. Чтобы установить последнюю версию Vuex, запустите следующую команду:

$ npm install vuex

и сохраните запись о ней в файл package.json зоомагазина.

Теперь нужно добавить хранилище в файл main.js, который находится в папке src. Самого хранилища пока не существует, но мы все равно его импортируем. Обычно оно находится в файле src/store/store.js, но вы можете выбрать другой путь — у всех разработчиков свои предпочтения. Остановимся на общепринятом варианте. Позже в этой главе мы обсудим альтернативную структуру каталогов с применением модулей.

Нужно добавить хранилище в корневой экземпляр Vue.js снизу от маршрутизатора, как показано в листинге 10.8. Между прочим, мы используем стандарт ES6, поэтому store: store можно сократить до store.

image

После подключения хранилища к корневому экземпляру можем обращаться к нему с любого участка приложения. Создайте файл src/store/store.js. В нем мы разместим хранилище Vuex с информацией о товарах, предлагаемых зоомагазином. Вверху добавьте два выражения import, по одному для Vue и Vuex. Затем укажите Vue.use(Vuex), чтобы соединить все вместе.

Мы импортировали хранилище в файле main.js из ./store/store. Теперь нужно экспортировать объект store внутри store.js. Как видно в листинге 10.9, мы экспортируем значение const store, равное Vuex.Store.

Сначала добавим объекты с состоянием и мутациями. Состояние будет содержать пустой объект под названием products. Вскоре мы наполним его с помощью метода initStore. Мутация называется SET_STORE, она станет присваивать переданные товары свойству state.products. Вставьте код из следующего листинга в файл src/store/store.js, который мы только что создали.

image

Нам нужно создать в хранилище действие и геттер. Геттер будет возвращать объект products. С действием все немного сложнее. Следует перенести хук created, который считывает файл static/products.json с помощью Axios, в объект actions внутри Vuex.

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

Создайте объект actions в файле store.js и добавьте в него метод initStore. Скопируйте в этот метод содержимое хука created из файла components/Main.vue. Вместо присваивания response.data.products объекту products мы вызовем мутацию с помощью функции commit. Передадим response.data.products в качестве аргумента для SET_STORE. Итоговый код должен выглядеть следующим образом (листинг 10.10).

image

Мы почти закончили, осталось только обновить файл Main.vue и перенести товары из локального объекта products в хранилище Vuex. Откройте файл src/components/Main.vue и найдите функцию данных. Удалите строчку products: {}. Мы будем обращаться к товарам из вычисляемого свойства, которое возвращает хранилище.

Найдите вычисляемые свойства cartItemCount и sortedProducts внутри Main.vue, они должны идти сразу после раздела methods. Добавьте свойство products и сделайте так, чтобы оно возвращало одноименный геттер.

Мы подключили хранилище к корневому экземпляру Vue.js в файле main.js, поэтому импортировать его больше не нужно. Кроме того, при использовании Vue-CLI хранилище всегда доступно в виде this.$store. Не забудьте знак $, иначе получится ошибка. Добавьте вычисляемое свойство products, как показано в листинге 10.11.

image

Найдите хук created, в котором инициализируется объект products, и удалите его содержимое. Вместо этого вставьте вызов действия initStore, которое мы создали ранее в хранилище Vuex. Чтобы вызвать действие, используйте функцию dispatch, как это сделано в предыдущем примере. В листинге 10.12 показано, как должен выглядеть хук created после обновления файла Main.vue.

image

Этого должно быть достаточно. Выполните в терминале команду npm run dev, и на экране должно появиться окно с приложением зоомагазина. Попробуйте положить товары в корзину и убедитесь в том, что все работает как следует. Если что-то пошло не так, поищите ошибки в консоли. В файле src/store/store.js вместо Vuex.store можно случайно набрать Vuex.Store. Помните об этом!

10.5. Вспомогательные методы Vuex


Vuex предоставляет удобные вспомогательные методы, которые позволяют сделать код более лаконичным и избавиться от добавления одних и тех же геттеров, сеттеров, мутаций и действий. Полный список вспомогательных методов Vuex есть в официальном руководстве по адресу vuex.vuejs.org/guide/core-concepts.html. Посмотрим, как они работают.

Основной вспомогательный метод, о котором вы должны знать, называется mapGetters. Он применяется для добавления всех имеющихся геттеров в раздел computed и не требует перечисления каждого из них. Но прежде, чем использовать, нужно импортировать его внутрь компонента. Еще раз вернемся к зоомагазину и добавим метод mapGetters.

Откройте файл src/components/Main.vue и отыщите тег script. Где-то внутри этого тега должен импортироваться компонент Header. Подключите mapGetters сразу после этого импорта, как показано в листинге 10.13.

image

Теперь нужно обновить вычисляемое свойство. Найдите в разделе computed функцию products и вставьте вместо нее объект mapGetters.

mapGetters — уникальный объект, для его правильной работы необходимо использовать оператор spread из состава ES6 — он расширяет выражение в ситуации, когда ожидается получение каких-либо аргументов (ноль и больше). Подробнее об этом синтаксисе можно почитать в документации MDN по адресу developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Operators/Spread_syntax.

mapGetters сделает так, что все геттеры будут добавлены в виде вычисляемых свойств. Это куда более простой и элегантный способ по сравнению с написанием отдельного вычисляемого свойства для каждого геттера. Все геттеры перечислены в массиве mapGetters. Добавьте этот вспомогательный метод в файл Main.vue (листинг 10.14).

image

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

Существует еще три вспомогательных метода, о которых вам следует знать: mapState, mapMutations и mapActions. Все они работают схожим образом, уменьшая объем шаблонного кода, который приходится писать вручную.

Представьте, что в вашем хранилище находится несколько фрагментов данных и доступ к состоянию осуществляется напрямую из компонента, без использования каких-либо геттеров. В этом случае вы можете применить метод mapState внутри раздела computed (листинг 10.15).

image

Теперь представьте, что в компоненте требуется использовать несколько мутаций. Чтобы упростить этот процесс, задействуйте вспомогательный метод mapMutations (листинг 10.16), как это сделано с mapState и mapGetters. Далее mut1 привязывает this.mut1() к this.$store.commit('mut1').

image

Напоследок рассмотрим вспомогательный метод mapActions. Он позволяет добавить в приложение действия Vuex, избавляя от необходимости создавать методы с вызовом dispatch в каждом отдельном случае. Возвращаясь к предыдущему примеру, представим, что приложение содержит какие-нибудь асинхронные операции. Поскольку использование мутаций исключено, мы должны прибегнуть к действиям. После создания их в Vuex нужно получить к ним доступ в объекте methods компонента. Эту задачу можно решить с помощью mapActions. act1 привяжет this.act1() к this.$store.dispatch('act1'), как показано в листинге 10.17.

image

Напоследок рассмотрим вспомогательный метод mapActions. Он позволяет добавить в приложение действия Vuex, избавляя от необходимости создавать методы с вызовом dispatch в каждом отдельном случае. Возвращаясь к предыдущему примеру, представим, что приложение содержит какие-нибудь асинхронные операции. Поскольку использование мутаций исключено, мы должны прибегнуть к действиям. После создания их в Vuex нужно получить к ним доступ в объекте methods компонента. Эту задачу можно решить с помощью mapActions. act1 привяжет this.act1() к this.$store.dispatch('act1'), как показано в листинге 10.17.

image

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

10.6. Краткое введение в модули


В начале этой главы мы создали файл store.js в каталоге src/store. Для небольшого проекта такой подход вполне уместен. Но что, если мы имеем дело с куда более крупным приложением? Файл store.js быстро разрастется, и будет сложно уследить за всем, что в нем происходит.
Для решения этой проблемы Vuex предлагает концепцию модулей. Модули позволяют разделить хранилище на несколько частей меньшего размера. У каждого модуля есть свои состояния, мутации, действия и геттеры, можно даже делать их вложенными друг в друга.

Перепишем зоомагазин с использованием модулей. Файл store.js останется на месте, но рядом с ним следует создать папку modules и поместить туда файл products.js. Структура каталогов должна выглядеть так, как на рис. 10.6.

imageВнутри products.js нужно создать четыре объекта: state, getters, actions и mutations. Содержимое каждого из них следует скопировать из файла store.js.

Откройте файл src/store/store.js и начните копировать из него код. Когда закончите, файл products.js должен выглядеть следующим образом (листинг 10.18).

Теперь нужно экспортировать весь код, который мы добавили в файл product.js. Это позволит импортировать его в store.js. Внизу файла добавьте выражение export default. Это инструкция экспорта в формате ES6, которая позволит импортировать данный код из других файлов (листинг 10.19).

Файл store.js следует обновить. Добавим в него объект modules, внутри которого можно будет перечислить все новые модули. Не забудьте импортировать файл modules/products, который мы создали ранее.

image

Наш пример содержит лишь один модуль, поэтому сразу добавим его в объект modules. Нужно также удалить все лишнее из Vuex.Store, как показано в листинге 10.20.

image

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

В некоторых крупных проектах разбиение на модули способно вызвать определенные проблемы. По мере добавления новых модулей могут возникнуть конфликты с именами действий, геттеров, мутаций и свойств состояния. Например, вы можете случайно присвоить одно и то же имя двум геттерам в разных файлах. И, поскольку в Vuex все находится в общем глобальном пространстве имен, в консоли возникнет ошибка дублирования ключа.
Чтобы избежать этой проблемы, поместите каждый модуль в отдельное пространство имен. Для этого достаточно указать namespaced: true вверху Vuex.store. Подробнее о данной возможности читайте в официальной документации Vuex на странице vuex.vuejs.org/ru/guide/modules.html.
» Более подробно с книгой можно ознакомиться на сайте издательства
» Оглавление
» Отрывок

Для Хаброжителей скидка 25% по купону — Vue.js

По факту оплаты бумажной версии книги на e-mail высылается электронная версия книги.
Tags:
Hubs:
+14
Comments6

Articles

Information

Website
piter.com
Registered
Founded
Employees
201–500 employees
Location
Россия