Админка магазина на vue.js. Урок 7. Flux и Vuex
Сегодня речь пойдет о flux и vuex. Flux - это подход для работы с данными во фронтенде. Vuex - это реализация flux для vue.js, точно так же, как и redux для реакта. Я иногда читаю о том, что flux - это новая концепция, парадигма, сложная идея и так далее. Поразбиравшись с flux, я не проникся трепетным отношением к нему, но оценил простоту подхода.
Если вы хотите узнать о flux для того, чтобы умными словами беседовать с тимлидом на собеседовании, лучше почитайте в википедии. У меня другая цель - научиться работать с vue.js удобно и комфортно. Решать задачи с помощью технологии, а не искать себе новые проблемы. Vuex - это именно та штука, которая решает проблемы, а не создает их.
Идея vuex состоит в том, что общие данные приложения хранятся в глобальном хранилище (читай, в глобальном объекте) и меняются эти данные через специальные функции. Вот вам вся идея, можете бить палками за попранные мечты о новой концепции.
Да, хранилище vuex - это тупо глобальный объект и некоторая обвязка вокруг него. То есть это та самая глобальная переменная, которой сеньоры пугают начинающих фронтедщиков. Простите, но принципиально vuex не отличается от localStorage.getItem и localStorage.setItem. Отличия в деталях, которые задают общий стиль работы с таким глобальным хранилищем. Давайте будем говорить "хранилище", а то "глобальная переменная" звучит совсем стремно. Мы же программисты, хочется работать с чем-то серьезным.
Теперь ближе к практике. Vuex состоит из таких основных субстанций:
— состояние (state)
— геттеры (getters)
— мутации (mutations)
— действия (actions)
Теперь по очереди, что это такое и как использовать.
State - состояние
Состояние state - это и есть глобальный объект, который хранит общие данные. Например, в нашей админке можно представить state так
const state = { currentPage: 'Products' products: [], categories: [], brands: [] }
Ничего особенного, обычный объект. Полям задаются дефолтные значения, а в процессе работы они заполняются нужными данными.
Хитрость в том, что во vuex не стоит кидать ВСЕ данные. А стоит только те, которые используются в разных частях приложения. Какие данные используются, какие нет, решать вам. Решает здравый смысл. Согласитесь, список брендов может использоваться где угодно, а флаг isOpened компонента NewProductPopup, показывающий, открыто ли диалоговое окно добавление товара, используется только в этом компоненте NewProductPopup.
В любом компоненте приложения мы имеем к доступ к хранилищу state. Обычно это делается через вычисляемые поля, когда в компоненте прописывается вот так
computed: { brands () { return this.$store.state.brands; } }
this.$store - это и есть то хранилище, которое появляется у каждого компонента vue при подключении vuex. Поле state всегда стандартно, когда хотим достучаться до какого-то объекта хранилища. brands понятно, само название поля. Кстати, вложенность объектов может быть любая.
Больше про state сказать особо нечего, следующая тема - геттеры.
Getters - Геттеры
Работают и как свойства, и как функции. Как свойста - это аналоги вычисляемых полей. Например, можно объявить геттер getAmountBrands и тогда вместо
computed: { amountBrands () { return this.$store.state.brands.length; } }
можно писать
computed: { amountBrands () { return this.$store.getters.getAmountBrands; } }
Не особо наглядный пример, но когда вам нужны будут вычисления сложнее размера массива, вы вспомните про геттеры и начнете их добровольно использовать.
Еще удобны геттеры, которые работают, как функции. Им можно передать данные как параметры и использовать их как обычные функции.
На конкретных примерах работу с геттерами (и в целом с vuex) мы разберем в следующей статье.
Mutations - Мутации
Третья важная вещь - мутации (mutations). Они нужны для изменения хранилища. Если вы меняете общие данные в vuex, то только через мутации. Чаще всего вы будете видеть примерно такой код
// Объявление мутации const mutations = { SET_BRANDS (state, brands) { state.brands = brands; } }; // Вызов мутации в компоненте context.commit('SET_BRANDS', brands);
SET_BRANDS - просто название функции, которая принимает 2 параметра: state и сами данные, в нашем случае коллекция брендов. Почему капсом? Я однажды прочитал идею, что если называть мутации или действия капсом, то в приложении будет легче ориентироваться. Попробовал - действительно работает. Субъективно, конечно, но мне понравилось, буду делать так. Сразу видно: ага, капс - значит, здесь меняются данные. С мутациями все, переходим к последней теме - действия.
Actions - Действия
Надо признать, я не сразу вкурил, что это такое и зачем они вообще нужны. Читая интернеты, я постоянно натыкался на дурацкие примеры с setTimeout и страшными словами вроде "асинхоронный". Потом наконец дошло.
Действия нужны для того, чтобы отправить ajax-запрос на сервер, получить от него данные и уже с этими данными вызвать мутацию. Разберем подробнее.
Допустим, у нас есть в state поле brands. По умолчанию это пустой массив, но при старте приложения мы хотим получить массив брендов с сервера из mysql-таблички и закинуть этот массив в этот state.brands. Что мы при этом делаем? Создаем действие getBrands
const actions = { getBrands (context) { axios .get('/api/v1/brands') .then(response => { context.commit('SET_BRANDS', response.data) }); } };
То есть идем на сервер через axios, получаем данные в response.data и устанавливаем их в state.brands. Как мы помним - только через мутацию
context.commit('SET_BRANDS', response.data)
А в приложении в корневом компоненте App при старте вызываем это действие. Вот так
created () { this.$store.dispatch('getBrands'); }
То есть схема такая: действие -> ajax-запрос -> ждем ответ от сервера -> мутация. Даже если нужно изменить данные без запроса на сервер, ученые мужи советуют все равно создать действие, которое просто вызовет мутацию. Смысл в этом есть. Вроде и чуть больше лишнего кода, но зато гораздо легче осознавать, что единственный способ изменить данные из компонента - вызвать действие. А действие уже разберется, нужно ли отправлять запросы на сервер или просто закинуть данные в state через мутацию. На тестовом приложении это оверхед, но когда в хранилище будут лежать сотни полей, вашим мозгам придется туговато вспоминать для каждого случая: а это через мутацию или действие? Не мучайте себя - создавайте действия и избавитесь от таких проблем.
Что в итоге?
Flux и vuex - это не какая-то супер концепция, сложная и мудрая. Это простой подход, облегчающий работу с общими данными в больших приложениях. И набор разумных правил, соблюдая которые, вам будет легче управлять приложением.
Нужно усвоить лишь несколько базовых вещей.
1. Выносите общие данные приложения в state. Общие - это те, которые могут использоваться в любом месте приложения.
2. Данные из state можно получать напрямую или через геттеры. Геттеры могут работать как вычисляемые свойства компонентов и как функции, принимающие параметры.
3. Чтобы изменить данные в хранилище, вызывайте действия. Действия выполняют нужные ajax-запросы, а по их завершению меняют сами данные в state.
4. Данные в state меняются только через мутации.
Это основные моменты. Конечно, в каждой реализации flux-подхода есть свои фишки, но идея везде одинакова. Во vuex есть свои удобные штучки, например, модули и неймспейсы, в redux свои.
Вы можете спросить: а зачем так сложно? Ведь можно же просто прокидывать данные в props дочерних компонентов? Можно. Но представьте, через сколько компонентов придется тащить данные с ростом приложения.
Вот живой пример. У нас есть список товаров, а в нем дочерний компонент - фильтры, который использует в селекте список брендов. Хорошо, завели в фильтрах бренды, получили их с сервера, все хорошо. А дальше мы создаем компонент добавления нового товара, а в нем компонент выбора бренда из списка. Так мы приходим к тому, что свойство брендов нужно создавать в компоненте товаров, а потом прокидывать их в фильтры и в добавление товаров, а из добавления в компонент выбора бренда. Вот такая схема:
Products // здесь храним бренды ProductsFilters ProductsNew ProductsNewSelectBrand
Во всех четырех компонентах нужны бренды и нужно их прокидывать из Products. А теперь рядом с Products создадим страницу Brands, где выведем список всех брендов и добавляем новые. Схема уже такая
Products // здесь храним бренды ProductsFilters ProductsNew ProductsNewSelectBrand Brands // здесь тоже нужны бренды
Но vue не может передавать данные в соседние компоненты. Значит, бренды нужно переносить на уровень выше, допустим в App
App // переносим бренды сюда Products ProductsFilters ProductsNew ProductsNewSelectBrand Brands
То есть в корневом компоненте App объявляем и получаем бренды и тащим потом их дальше через все приложение. Причем некоторым компонентам эти бренды нафиг не нужны, например, Products и ProductsNew. Они просто прокидывают бренды дальше в дочерние компоненты. Чувствуете, сколько уже мороки только для чтения общих данных?
А теперь еще хуже, в одном из компонентов мы добавили новый бренд, отправили его на сервер, но еще же нужно обновить данные и локально. Представьте, через сколько компонентов нужно тащить этот только созданный бренд до корневого App, чтобы уже там добавить в brands, откуда он "разойдется" по всем дочерним. Это сколько emit-ов нужно написать для такой простой задачи!
Да, частично проблему обновления можно решить через шину событий, но она все равно не избавит от необходимости пробрасывания props в дочерние.
А vuex решает проблему чтения и обновления глобальных данных вот так
// Чтение computed: { brands () { return this.$store.state.brands; } } // Запись this.$store.dispatch('addBrand', brand);
Надеюсь, я вас убедил, что flux, и в частности vuex хоть и модная, но полезная штука. И vuex точно стоит потраченного на изучение времени.
Как писать код с помощью vuex для нашей админки интернет-магазина, мы рассмотрим уже в следующей статье. Хотел сделать все в одной, но получится перегруз. Поэтому в этот раз только общие идеи и вопросы vuex простыми словами. Насколько получилось простыми, судить вам.
До встречи!
Все уроки админки на vue.js
- Урок 1. Список товаров
- Урок 2. Фильтры и сортировки
- Урок 3. Новое REST API на чистом PHP
- Урок 4. Правим клиентский код под новое REST API и находим багу
- Урок 5. Разбиваем приложение на компоненты
- Урок 6. Инструмент vue-cli и vue-компоненты
- Урок 7. Flux и Vuex - общие вопросы
- Урок 8. Vuex на практике
- Урок 9. Перерабатываем фильтры
- Урок 10. Добавляем и удаляем бренды
- Урок 11. Обрабатываем ошибки на клиенте и сервере
- Урок 12. Редактируем бренды
- Урок 13. Роутинг
- Урок 14. Карточка товара
Истории из жизни айти и обсуждение кода.