FurryCat 07 декабря 2019

Redux и Vuex: правая и левая палочки Twix

Абсолютно разные создатели, совершенно разные фабрики и такие разные библиотеки для управления состоянием. Попробуй обе и реши – на чьей стороне ты?
Redux и Vuex: правая и левая палочки Twix

Redux и Vuex – это две совершенно разные библиотеки или одно и то же в разной обертке? Пора разобраться в этой загадке. Начнем с начала – с понимания задачи, которую они пытаются решить.

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

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

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

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

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

Каждый компонент приложения взаимодействует напрямую с хранилищем состояния
Каждый компонент приложения взаимодействует напрямую с хранилищем состояния

Чем ветвистее дерево компонентов, тем очевиднее преимущества такой схемы. Если что-то идет не так, вы всегда знаете, где искать проблему. Такие проекты легко тестировать и расширять, так как разные компоненты мало зависят друг от друга.

Одна цель

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

Хранилище состояния

И в Redux, и во Vuex данные приложения хранятся в виде обычного объекта. На самом деле, в JavaScript довольно сложно придумать другой способ, так что особого выбора у них не было.

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

Redux

Создадим хранилище Redux:

        const store = new Store(reducers, initialState);
    

В reducers передается набор обработчиков, изменяющих данные. Как именно происходит этот процесс, мы увидим чуть позже. initialState – это исходные данные, которые нужно положить в хранилище в самом начале работы.

У хранилища в Redux есть несколько полезных методов:

  • getState() – получение актуального состояния;
  • dispatch() – вызов действия для изменения состояния;
  • subscribe() – подписка на изменения.
Хранилище в Redux и его взаимодействие с компонентами
Хранилище в Redux и его взаимодействие с компонентами

Vuex

А вот так создается хранилище во Vuex:

        new Vuex.Store({
  state: {},
  getters: {},
  mutations: {},
  actions: {},
});
    

Сами данные хранятся в объекте state. getters, как вы, вероятно, догадались, будет содержать геттеры для получения информации. С mutations и actions разберемся чуть позже, сейчас достаточно понимать, что их основное предназначение – изменять данные в хранилище.

Одна идея

Между хранилищами в Redux и Vuex нет принципиальной разницы, но есть организационная. Объект с данными прячется внутри некоторого Store и его изменение осуществляется только опосредованно – через мутации (Vuex) или действия (React). Это позволяет защитить данные от неконтролируемых прямых изменений.

Перейдем к реализации взаимодействия компонентов с хранилищем.

Получение данных

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

Redux

Redux обзавелся специальным методом getState(), который возвращает все состояние приложения разом.

        <HelloWorld text={store.getState().text} />
    

Для отслеживания изменений данных и реакции на них обычно используется какая-нибудь дополнительная система UI-биндинга, например, библиотека react-redux (при использовании Redux в React-приложениях). Но при желании вы можете напрямую подписаться на обновления состояния с помощью метода subscribe.

        store.subscribe(() => console.log(store.getState()))
    

Vuex

Во Vuex вы можете обратиться к свойствам напрямую, достав их из store.state:

        computed: {
  count() {
    return this.$store.state.count;
  }
}
    

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

Пример из документации:

        // определяем геттер при создании хранилища

const store = new Vuex.Store({
  state: {
    todos: [ ]
  },
  getters: {
    doneTodos: state => {
      return state.todos.filter(todo => todo.done);
    }
  }
});

// затем обращаемся к нему в компоненте

computed: {
  doneTodosCount () {
    return this.$store.getters.doneTodosCount
  }
}
    

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

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

Разные методы

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

Изменение данных

Переходим к самому интересному – изменению данных в хранилище. Это ключевой момент, влияющий на все приложение. Здесь важно сохранить новые данные, не потеряв при этом все остальное.

Redux

В основе философии Redux лежит понятие иммутабельности (неизменяемости). Нельзя изменить объект с данными, можно только заменить его целиком. Никаких сайд-эффектов, никаких неприятных сюрпризов – только чистый функциональный подход.

В Redux есть лишь один законный способ изменить состояние приложения – совершить действие (actions).

Экшн – это самый обычный объект, который описывает происходящее изменение. Например, он может выглядеть вот так:

        let action = {
  type: 'ADD_USER',
  user: {name: 'Dan'}
};
    

Поле type – обязательное, оно содержит тип действия. Все остальное зависит только от вас.

Теперь это действие нужно передать в хранилище с помощью метода dispatch:

        store.dispatch(action);

    

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

        const someReducer = function(state, action) {
  ...
  return newState;
}
    

Все очень просто:

  1. Какой-нибудь компонент хочет изменить состояние и диспатчит соответствующий экшн.
  2. Оно прогоняется через редьюсеры, которые анализируют тип действия и вносят необходимые изменения.
  3. Возвращенный цепочкой редьюсеров объект становится новым состоянием приложения.

Философия Redux требует, чтобы редьюсеры соответствовали концепции чистых функций (pure functions), то есть не имели никаких побочных эффектов и не зависели от внешних условий.

Vuex

Во Vuex тоже всего один законный способ изменять состояние – мутации (mutations).

Каждая мутация имеет собственное название и соответствующую ей функцию-обработчик. Все мутации описываются при инициализации хранилища.

        const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    increment (state, count) {
      state.count += count
    }
  }
});
    

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

Вызов мутации во Vuex очень похож на вызов экшна в Redux:

        store.commit('increment', 10);
    

Каждая мутация состояния отслеживается и реактивно передается компонентам.

Разные понятия

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

Redux несколько серьезнее относится к отсутствию сайд-эффектов, Vuex предоставляет несколько более интуитивный интерфейс.

Асинхронное изменение данных

Асинхронность – одна из основных концепций JavaScript в целом. Асинхронных действий в вебе не меньше (а может и больше), чем синхронных. Представим простую асинхронную задачу.

Есть компонент-кнопка, при нажатии на которую отправляется запрос на сервер. Пока не получен ответ, нужно вывести прелоадер, чтобы пользователь знал, что что-то происходит. Затем нужно отобразить полученные данные (например, вывести табличку) или показать ошибку, если запрос завершился неудачно.

Redux

В Redux нет асинхронной функциональности из коробки.

Чтобы сделать это придется использовать миддлвары, расширяющие функциональность хранилища (к счастью, все уже давно и хорошо написано за нас).

Vuex

Мутации в Vuex тоже являются синхронными, внутри них нельзя отправлять запросы к серверу. Но у библиотеки тут есть козырь в рукаве – действия (не путать с действиями в Redux).

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

        const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment(state) {
      state.count++;
    }
  },
  actions: {
    increment(context) {
      context.commit('increment');
    }
  }
});
    

Вызвать такое действие не сложнее, чем запустить мутацию:

        store.dispatch('increment');
    

Внутрь действия может быть помещена любая асинхронная логика.

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

Обратите внимание: действия являются лишь оберткой над мутациями – единственным законным способом изменения состояния.

Разные возможности

Что ж, тут Vuex из коробки обошел Redux, создав собственную (и весьма простую!) асинхронную функциональность.

Правая и левая палочки Twix

Redux и Vuex – это две палочки Twix. Они производятся на разных фабриках и по уникальным технологиям, но в итоге мы имеем очень похожие продукты.

На палочку Redux карамель льется волнами, а на палочку Vuex – струится:

  • Redux многословнее и строго придерживается принципов функционального программирования.
  • Vuex понятнее для изучения и проще при разработке.

На палочку Redux шоколад наливается вертикально, а на палочку Vuex – сверху вниз:

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

А чему отдаете предпочтение вы?

Комментарии

ВАКАНСИИ

Добавить вакансию
Разработчик C++
Москва, по итогам собеседования

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ