Как уменьшить количество и увеличить читаемость кода в react-redux, redux-saga

В этой статье я хотел бы поделиться своим опытом использования связки react-redux и redux-saga, а точнее, какой «велосипед» я использую, для уменьшения количества однотипного кода и упрощению его восприятия.



Что меня не устраивало


Библиотеки react-redux и redux-saga просты, гибки и удобны, однако имеют избыточность кода. Основные элементы это:


  1. Фабрики событий




    В таком виде меня смущает несколько вещей:


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

    — если вы забыли структуру пайлоада(payload), что бы его вспомнить, надо перейти к reducer/saga, где используется это событие, и посмотреть что там нужно передавать
  2. Редьюсеры




    Тут в целом напрягает только использование конструкции switch


  3. Саги




    В сагах самая большая из проблем, сложность чтения. Просто беглым взглядом понять, что делает сага сложно. Одна из причин этому — глубокая вложенность. Можно конечно вынести воркеры, что бы уменьшить глубину, однако тогда увеличивается избыточность.



    Так же, есть проверки, которые или увеличат вложенность, или добавят точки выхода из саги, что ухудшает наш код.



Как я пытаюсь решить эти проблемы


Давайте по порядку.


  1. Фабрики событий




    создаем класс с аннотацией actionsCreator(). При создании экземпляра класса, полям не имеющим значения(initialize;/onFieldChange;/handleField;/gpToNextStep;) будет присвоен привычный нам action creator. Если событие содержит данные, имена полей передаем через аннотацию payload(...[fieldNames]). После преобразования предыдущий пример будет выглядеть вот так:



    так же у полей будут переопределены методы toString, toPrimitive, valueOf. Они будут возвращать строковое представление типа события:



  2. Редьюсеры




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



  3. Саги




    создаем класс с аннотацией sagas(). При создании экземпляра класса получаем генератор функций, вызывающий все поля класс помеченных аннотацией takeEvery([...[actionTypes]]) или takeLatest([...[actionTypes]]) в отдельном потоке:



    так же с полями можно использовать аннотацию filterActions({ state, type, payload }), в этом случае сага будут вызвана только если функция вернет true.



Заключение


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


Эти аннотация я вынес в пакет sweet-redux-saga. Если есть другие решения, буду рад, если поделитесь со мной.