Redux Toolkit больше не нужен?

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

    История появления


    Наша компания занимается разработкой проектов для стартапов по всему миру. А для этих сумасшедших вдохновенных ребят самым важным (или один из таковых) является время. Раньше вышел на рынок — выше шансы выжить. Поэтому мы всегда стараемся сократить время разработки, не жертвуя при этом качеством, и уменьшить количество ошибок, вызванных человеческим фактором. По большей части выбираем что-то стандартное и более или менее стабильное. В данном случае ничего готового найти не удалось, поэтому засучили рукава и начали смотреть, что тут можно навелосипедить.



    Как водится, начали с описания проблемы и получили следующее:

    • Каждый разработчик имеет свой, едва уловимый акцент при написании кода, трудно добиться реализации концепции «обезличенный код»;
    • Много кода (бойлерплейт, копипаста и все вытекающие проблемы), 100+ строк на CRUD;
    • Много времени уходит на написание базовых вещей. То одно забудешь, то другое: выставить loading, обработать ошибки и т.д.;
    • Структура стора отличается от проекта к проекту, а иногда и между разными частями проекта.

    Правильно заданный вопрос или сформулированная проблема — половина решения. Еще немного подумав, ввели регламент для формирования стора, описали его структуру и решили отрефакторить и причесать код. Несмотря на то, что наша кодовая база не была неимоверно большой, сразу же споткнулись о количество кода, который нужно изменить.

    Пока ПМ-ы ломали головы над тем, откуда взять еще пару другую часов в сутках, нашлась пара энтузиастов, которые сгенерировали и воплотили в жизнь простую до банальности идею. Да-да, гениальные идеи, после того как придумываются, всегда кажутся простыми. А идея заключалась в следующем: просто-напросто генерировать отдельные куски кода вместо того, чтобы переписывать его вручную. Использовать сниппеты и получать на выходе простыни не хотелось, т.к. любые изменения приводили к катастрофе и повторным рефакторингам, поэтому по-быстрому запилили функцию, которая принимала на вход некие параметры и строила reducer, action и saga. Так родилась первая версия коммуникаций (@axmit/redux-communications). Название “коммуникации” родилось как-то само собой, т.к. эта библиотека связывает воедино стор, саги и компоненты. Так и пошло …

    Сразу после этого наступило счастье. Ну или почти сразу. Время разработки уменьшилось примерно на 20% (как мы это посчитали — тема отдельной статьи). В первую очередь — за счет значительного сокращения количества вылезающих багов. И это не говоря о том, что разработчики стали гораздо реже допускать глупые ошибки по невнимательности.



    Линус Торвальдс как-то сказал: «Болтовня ничего не стоит. Покажите мне код.», и я полностью с ним согласен. Не буду цитировать и переписывать доку или приводить здесь простыни кода, приведу лишь небольшой пример. Ссылки на полное описание библиотеки и песочницу с примерами также можно найти в конце статьи.

    Рассмотрим типичную задачу — нам нужно создать CRUD по какой-то сущности. Возьмем для примера задачу(task). Описывать стандартный вариант считаю бесполезным, т.к. он займет много места, и все, кто сталкивался с редаксом, скорее всего примерно представляют, как это будет выглядеть. А чтобы получить коммуникацию, нужно сделать следующие вещи:

    1. Описать транспорт, без этого никуда

    const basePath = `/api/task`;
    
    const taskTransport = {
       add: task => axios.post(`basePath`, task).then(r => r.data),
       get: id => axios.get(`${basePath}/${id}`).then(r => r.data),
       update: task => axios.put(`${basePath}/${task.id}`, task).then(r => r.data),
       delete: id => axios.delete(`${basePath}/${id}`, task),
       getCollection: () => axios.get(basePath).then(r => r.data)
    };
    

    2. Описать имя namespace
    const namespace = 'task';
    

    3. Создать стратегию для создания коммуникации
    const strategy = new CRUDStrategy({
       namespace,
       transport: taskTransport
    });
    

    4. Создать коммуникацию
    const taskCommunication = buildCommunication(strategy);
    

    5. Подключить редьюсеры и саги из коммуникации, как обычно
    taskCommunication.reducers
    taskCommunication.sagas
    

    6. После этого остается подключить стор к компоненту
    taskCommunication.injector(MyComponent)
    

    7. И начать пользоваться
    componentDidMount() {
       const { getTaskCollection } = this.props;
       getTaskCollection();
    }
    
    render() {
       const { taskCollection } = this.props;
       return  taskCollection.data.map(item => <span>{item.title}</span>)
    }
    

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

    import { taskTransport } from './task.transport';
    import { CRUDStrategy, buildCommunication, StoreBranch } from '@axmit/redux-communications';
     
    const namespace = 'task';
     
    const strategy = new CRUDStrategy({
       namespace,
       transport: taskTransport
    });
     
    export const taskCommunication = buildCommunication(strategy);
    


    Это все, что нужно сделать, чтобы иметь полностью работоспособный CRUD. Если нужно сделать что-то более сложное — можно расширить CRUD коммуникацию или использовать BaseCommunication. В крайнем случае, под капотом это все тот же старый-добрый редакс со всеми его возможностями. Гибкость не пострадала. Транспорт вынесен в отдельный слой, и его реализация может быть какой угодно. У нас на проектах есть graphQL и простые запросы с использованием axios, и трудностей в этом плане мы не испытывали. Внимательный читатель мог заметить, что библиотека экспортирует саги, и это является одним из самых существенных ее ограничений. Если по какой-то причине вы не можете использовать саги, данная библиотека, к сожалению, вам не подходит.

    Почему сейчас?


    Решение написать статью пришло после прочтения этой статьи. Потыкав данный инструмент, с удивлением для себя обнаружил, что коммуникации намного проще, более лаконичны, дают более четкую структуру стора и при этом не уступают в гибкости. Посидев и поразбиравшись часок с исходниками из примера к redux-toolkit, переписал его на коммуникации. Старался вносить минимум изменений, чтобы было проще отследить разницу, хотя, на мой взгляд, структура примера очень запутана. Комментарии по коду специально оставил, чтобы проще было сравнивать как было и как стало. Обратите внимание на файлы *.communication.ts и слайсы, которые они заменяют.



    То, что количество строк намного меньше и сам код гораздо приятнее выглядит(субъективно) не столь важно, т.к. в проде у нас бывают довольно тяжеловесные коммуникации. Тут есть одно более важное отличие. Код — декларативный. Мы просто определяем, что и откуда хотим получить и что сделать с данными, а как это сделать — нас совершенно не волнует.
    Вывод — redux-toolkit стоит можно использовать для кастомизации, для всего остального есть MasterCa... @axmit/redux-communications.

    Подведем итоги


    • Код стал единообразен на всех проектах и у всех разработчиков. Похожие проблемы решаются единообразно, а решения зачастую выносятся в отдельные пакеты и переиспользуются в будущем. При этом количество кода уменьшилось в разы.
    • Джуны получили четкий и понятный флоу.
    • Сеньоры радуются от того, что не надо писать тонны бойлерплейт-кода и думают, как еще можно улучшить коммуникации.
    • Отладка упростилась, а структура стора стала проста и понятна каждому разработчику в компании.
    • Переход между проектами или разными частями системы не вызывает головной боли.
    • ПМ-ы тоже рады. Количество багов уменьшилось — заказчики довольны. Писать стало проще — разработчики довольны. А что еще надо ПМ-у для счастья?
    • На коммуникации можно переезжать постепенно или вообще использовать гибридный подход(коммуникации + слайсы).

    Разумеется у библиотеки есть и недостатки.

    • Без примера кода и документации разобраться в ней довольно сложно.
    • Изменить структуру стора под произвольную не получится, т.к. именно на структуре стора базируется вся автоматизация. Но стоит отметить, что мы в своей работе никогда не испытывали с этим трудностей.
    • Работать можно только с сагами. Thunk нам не подходил, и мы всегда юзаем саги, поэтому для нас это не является проблемой. Но если по каким-то причинам саги вам не подходят, то и библиотекой пользоваться не получится.

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

    Полное описание библиотеки можно найти тут, а потыкать online тут.

    Полный код переписанного на коммуникации примера из доки ReduxToolkit тут, а потыкать можно тут.
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 7

      0

      За такой навороченной абстракцией redux уже и не узнать. Не было ли проще сделать эту штуку отдельной библиотекой, без зависимости от redux?

        +1
        Можно ли это это было сделать без зависимостей — наверно да. Было ли это сделать проще — однозначно нет. Библиотека полностью построена поверх Redux и всего лишь автоматизирует рутинные операции.
        Имея под капотом Redux мы можем пользоваться всеми инструментами, которые созданы для работы с ним, например Redux DevTools и это очень удобно.
        Какие профиты может дать отделение ее от Redux?
        0
        Я может не понял чего, но как это у вас в определении транспорта два свойства `update`?
          0
          Это опечатка. Поправил. Спасибо!
          0

          Спустя год после выхода React Hooks кто-то все ещё публикует статьи с классами. Мне вообще гораздо больше нравится Rematch как альтернативное API для Redux.

            0
            Цель статьи — показать как упростить работу с Redux, без привязки к фичам React. Кроме того, по ощущениям, больше людей знакомы с работой через классы. Крупные проекты, например, зачастую не спешат внедрять моментально все нововведения. Это сложно, долго, дорого и тд.
            Поэтому, для целей статьи, не важно хуки это будут или нет. В конце статьи приводятся ссылки
            Полный код переписанного на коммуникации примера из доки ReduxToolkit тут, а потыкать можно тут.

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

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.