Админка магазина на vue.js. Урок 7. Flux и Vuex

апрель 4 , 2019
Метки:
Предыдущая статья Следующая статья

Сегодня речь пойдет о 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

Предыдущая статья Следующая статья
Метки:
Заходите в группу в контакте - https://vk.com/webdevkin
Анонсы статей, обсуждения интернет-магазинов, vue, фронтенда, php, гита.
Истории из жизни айти и обсуждение кода.
Как Вам статья? Оцените!