Redux и Effector — это две популярные библиотеки, которые используются для  управления состоянием в веб-приложениях. Но если первая широко известна во frontend-разработке, вторая только набирает обороты. В этой статье мы рассмотрим, как работают оба инструмента, разберем преимущества и недостатки и дадим рекомендации по использованию на проектах.  Материал адресован frontend-разработчикам, которые ранее не сталкивались с Redux или хотят найти ему альтернативу.

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

Хорошее определение стейт-менеджерам, на наш взгляд, дал Павел Черторогов

Переменная + Функция + паттерн Observable

Принцип работы стейт-менеджеров
Принцип работы стейт-менеджеров

Вернемся к предмету нашего обзора. По состоянию на 10 августа 2023 года эти две библиотеки имеют следующие показатели:

Effector

Redux

Количество загрузок в неделю

34 493

6 873 885

Звезды на GitHub

~ 4.3K

~ 59.8K

Популярность

Менее популярен, но набирает обороты

Очень популярен

Сообщество

Относительно небольшое, но активное

Огромное и активное

Лицензия

MIT

MIT

Документация

Есть документация, но не очень подробная

Подробная документация

Сложность документации

Сложнее для новичков

Более простая для понимания

Тестирование

Легко тестируется

Сложнее тестировать

Производительность

Имеет хорошую производительность

Менее производителен

Redux 

Redux — это библиотека для управления состоянием приложения в React, разработанная Дэном Абрамовым и Эндрю Кларком в 2015 году. 

Основная идея Redux заключается в том, чтобы иметь единственный источник правды — хранилище (store), в котором хранятся все данные приложения. Действия (actions) и редьюсеры (reducers) используются для изменения состояния в хранилище.

Схема работы Redux
Схема работы Redux

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

Однако Redux имеет и некоторые недостатки. Одним из них является большое количество шаблонного кода, который нужно написать для создания действий и редьюсеров. Это может привести к увеличению объема кода и усложнению его поддержки. Давайте посмотрим на пример со счетчиком:

// actions.js
export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';

export const increment = () => {
  return {
    type: INCREMENT
  };
};

export const decrement = () => {
  return {
    type: DECREMENT
  };
};

// reducers.js
import { INCREMENT, DECREMENT } from './actions';

const initialState = {
  count: 0
};

const counterReducer = (state = initialState, action) => {
  switch (action.type) {
    case INCREMENT:
      return {
        ...state,
        count: state.count + 1
      };
    case DECREMENT:
      return {
        ...state,
        count: state.count - 1
      };
    default:
      return state;
  }
};

export default counterReducer;

// store.js
import { createStore } from 'redux';
import counterReducer from './reducers';

const store = createStore(counterReducer);

export default store;

// App.js
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './actions';

const App = () => {
  const count = useSelector(state => state.count);
  const dispatch = useDispatch();

  return (
    <div>
     <h1>Count: {count}</h1>
<button onClick={() => dispatch(increment())}>
  Increment
</button>
<button onClick={() => dispatch(decrement())}>
  Decrement
</button>
    </div>
  );
};

export default App;

В этом примере создается простой счетчик с помощью Redux. Файл actions.js содержит определение действий, файл reducers.js содержит определение редьюсера, файл store.js создает хранилище Redux, а файл App.js использует хуки useSelector и useDispatch для получения состояния и диспетчера из хранилища и отображает счетчик на странице.

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

Альтернатива — Redux-Toolkit

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

Возьмем для примера предыдущий код и перепишем его, используя Redux-Toolkit:

// actions.js
import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    count: 0
  },
  reducers: {
    increment: state => {
      state.count += 1;
    },
    decrement: state => {
      state.count -= 1;
    }
  }
});

export const { increment, decrement } = counterSlice.actions;

export default counterSlice.reducer;

// store.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './reducers';

const store = configureStore({
  reducer: {
    counter: counterReducer
  }
});

export default store;

// App.js
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './actions';

const App = () => {
  const count = useSelector(state => state.counter.count);
  const dispatch = useDispatch();

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={() => dispatch(increment())}>
        Increment
      </button>
      <button onClick={() => dispatch(decrement())}>
        Decrement
      </button>
    </div>
  );
};

export default App;

В этом примере используется функция createSlice из Redux Toolkit для создания среза (slice), который объединяет определение действий и редьюсера в одном месте. Затем этот срез передается в функцию configureStore для создания хранилища Redux. В компоненте App также используются хуки useSelector и useDispatch для получения состояния и диспетчера из хранилища. 

Effector 

Effector был создан в 2019 году Александром Корниенко и является отличной альтернативой Redux. Основная идеология заключается в использовании эффектов (effects) для управления побочными эффектами и изменениями состояния. 

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

  1. Приложения с большим количеством сложных состояний. Effector позволяет эффективно управлять состоянием приложения и обрабатывать сложные взаимодействия между компонентами.

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

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

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

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

А теперь давайте перепишем наш счетчик с использованием библиотеки Effector:

// store.js
import { createStore, createEvent } from 'effector';

const increment = createEvent();
const decrement = createEvent();

const counter = createStore(0)
  .on(increment, state => state + 1)
  .on(decrement, state => state - 1);

export { increment, decrement, counter };

// App.js
import React from 'react';
import { useStore } from 'effector-react';
import { increment, decrement, counter } from './store';

const App = () => {
  const count = useStore(counter);

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  );
};

export default App;

В этом примере мы используем функции createStore и createEvent из Effector для создания хранилища и событий. События increment и decrement вызываются при нажатии на соответствующие кнопки в компоненте App. Состояние счетчика хранится в хранилище counter, которое передается в хук useStore для получения текущего значения.

Кроме того, Effector обладает более эффективной архитектурой, чем Redux. Вместо использования одного глобального хранилища Effector использует граф зависимостей, который позволяет более точно определить, какие части приложения должны быть перерисованы при изменении состояния. Это позволяет улучшить производительность и уменьшить нагрузку на приложение. Также сообщество Effector предлагает при построении архитектуры использовать feature-sliced design. Это уже новый взгляд на архитектуру, достойный отдельной статьи.

Помимо Redux и Effector существуют и другие альтернативы для управления состоянием. Некоторые из них включают MobX, Apollo Client и Zustand. Каждая из этих библиотек имеет свои преимущества, недостатки и решает определенные задачи. Выбор зависит от конкретных потребностей вашего проекта.

Заключение

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

Можно ли обойтись совсем без менеджеров состояний? — Да! Если ваш проект не очень сложный. Например, для написания простого веб-приложения, такого как блог, лендинг, сайта-визитки с несложной логикой, рекомендуем вообще отказаться от менеджера состояний либо использовать React Context.

Спасибо за внимание!

Авторские материалы для frontend-разработчиков мы также публикуем в наших соцсетях – ВКонтакте и Telegram.

Комментарии (10)


  1. markelov69
    10.08.2023 07:59
    -2

    React + Redux/Effector - антиинструменты.
    React + MobX - инструменты.


    1. Pab10
      10.08.2023 07:59

      Не забывайте делать пометку, что это ваше личное мнение и около 7 000 000 людей в неделю с ним не согласны.


      1. anonymous
        10.08.2023 07:59

        НЛО прилетело и опубликовало эту надпись здесь


        1. Pab10
          10.08.2023 07:59
          -1

          Учитывая тот факт, что разработчики, в большинстве своем, это интеллектуальная элита общества и добавляя тот момент, что никто не хочет работать больше - не могут. Когда плюсы использования технологии очевидны - люди переходят на нее не задумываясь. В данном случае видимо есть что-то, что заставляет людей задуматься. Редукс - много кода, да. Но он решает проблему с ротацией команды на проекте. Все знают, как организован редуксовый стор, это паттрен. Как родили 10к мобиксовых сторов 10 команд, которые работали над проектом - никому не известно. Бизнесу нужна стабильность, а не хипстота, и сдается мне, что это и определяет перевес в сторону редукса в 7 раз.

          Оффтоп. Мне всегда была интересна мотивация людей, которые пишут провокационные, неаргументированные комментарии, сливают карму, регают новые аккаунты, чтобы продолжить срач и, вероятно, прикидываются другими участниками разговора, чтобы подогреть дискуссию. Вам оно зачем? Гемора много, лулзов мало. Реально есть еще кто горит?-)


          1. anonymous
            10.08.2023 07:59

            НЛО прилетело и опубликовало эту надпись здесь


      1. kubk
        10.08.2023 07:59

        Из этих 7 000 000 людей в неделю как минимум полтора миллиона приходится на библиотеку react-beautiful-dnd: https://www.npmjs.com/package/react-beautiful-dnd

        У нас например используется Mobx, но из-за библиотеки выше ещё устанавливается и Redux. Таких проектов может быт много. Более того, количество установок != количеству пользователей. Одна и та же библиотека может устанавливаться на CI сотни раз в день в зависимости от размера контрибьюторов в проект, настроен ли кеш.


    1. SakuraShiten
      10.08.2023 07:59

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


      1. anonymous
        10.08.2023 07:59

        НЛО прилетело и опубликовало эту надпись здесь


  1. Pab10
    10.08.2023 07:59
    -1

    Реквестирую более приближенные к жизни примеры - аргументы для евента, асинхронные движения в евентах, etc.


  1. Nurked
    10.08.2023 07:59
    +2

    Что меня дико прёт в мире вэб разработки, так это то, как условный Вася презентует новую технологию. Посмотреть на Васины кейсы - так цены ей нет. Код ужимается в 146%, размер ужатого жиэса - -5 килобайт. Юзеры орут как слоны, которые нашли арбузы. Всё просто прекрасно.

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