Привет, Хабр! Меня зовут Данил Чернышев, я разрабатываю альтернативу Agile и хочу поделиться своим проверенным стеком библиотек и технологий для скоростной разработки Web SaaS в 2025 году. Прошу профессиональное сообщество в комментариях поделиться своими рецептами для создания качественных программных продуктов.

Архитектура и требования к продукту

Мой сервис представляет собой систему управления продуктами. С точки зрения характера движения данных, он сходен с классическими системами управления проектами: Jira, ClickUp, Asana, Basecamp и так далее. Они представляют собой low-read и low-write сервисы, фактически коллекции документов, продуктовых спецификаций. Для них, в первую очередь, важна скорость доступа к данным – насколько быстро открывается тикет по клику с канбан-доски. По сравнению с условной социальной сетью, это не высоконагруженный тип продуктов. Вторым критическим фактором является эффективность и скорость поиска, что довело, к примеру, Atlassian до создания собственного языка для поиска, Jira Query Language (JQL). Особенности концепции моего продукта, в том числе отсутствие канбан-доски, позволили мне не внедрять поиск вовсе, по крайней мере, на начальных стадиях развития.

Очень многие небольшие стартапы в SaaS сфере выбирают Typescript / NodeJS / React экосистему в силу ее простоты и широкого ассортимента разнообразных библиотек. Мой проект не стал исключением и сейчас он представляет собой Typescript + React + Redux Toolkit фронтенд приложение, NodeJS + ExpressJS бекэнд приложение с MongoDB инстансом, сервер аутентификации Supertokens с базой Postres. Каждое приложение или база завернута в Docker контейнер и разворачивается одним bash скриптом, который устанавливает и настраивает Docker, скачивает и запускает образы, устанавливает реверс прокси Nginx и SSL сертификаты с помощью Let’s Encrypt. Инстансы приложения развернуты на Яндекс.Облаке и зарубежных сервисах для максимальной скорости для конечного пользователя по всему миру. Но так было не всегда, и на этом пути я сделал ряд ошибок. Начнем рассказ по порядку, с фронтенда.

Фронтенд

React начал набирать популярность в 2017 году, и уже в 2018 году у меня появилась возможность получить коммерческий опыт работы с ним. С тех пор кроме React у меня был небольшой опыт работы с Angular, AureliaJS (мало кто знаком с этим чудом природы) и VueJS. Ничего проще, чем React с его JSX, с тех пор не изобрели. Несмотря на то, что последние годы команда разработки React пытается флиртовать с серверными компонентами, переизобретая PHP, React остается единственным рабочим вариантом для быстрой разработки фронтенд приложений в 2025 году в силу огромной экосистемы библиотек и простоты.

State management – более щекотливая тема. Я работаю с Redux с 2018 года, и принципы единого источника правды (single source of truth) с однонаправленным движением информации (unidirectional flow, Flux pattern) служили мне верой и правдой много лет. Какое-то время назад Redux эволюционировал в Redux Toolkit c концептами слайсов и API - файлов, фактически вырвав почву из-под ног всех критиков Redux о том, что надо писать много бойлерплейта. В 2025 году Redux Toolkit не имеет никакого бойлерплейта и прост, как пять копеек. Кроме этого, мейнтейнеры Redux создали Redux Toolkit Query, интегрированную в Redux, которая берет на себя все аспекты HTTP запросов, их статуса и обработки ошибок. RTK Query покрывает 95 процентов всех юзкейсов моего фронтенд приложения. Библиотека основана на redux thunk, библиотеке управления сайд-эффектами. Ее подход проще, чем Redux Sagas, но в моем приложении нет сложной оркестрации API-запросов или race conditions, которые удобнее решать с помощью саг.

Справедливости ради хочу коснуться mobx, еще одной популярной библиотеки управлением состоянием. Главное отличие – большая гибкость, можно создавать бесконечное количество сторов с данными. У меня был небольшой опыт с mobx в 2020 году, и мне запомнилось только то, что в mobx есть пять взаимозаменяемых способов диспатчить action creator’ы. Зачем? Я так и не понял. Зачем несколько сторов, если достаточно одного? Тоже непонятно. Довод о том, что один стор Redux может стать слишком большим и влиять на производительность, несостоятелен – в 2019 году я складывал в Redux огромные geoJSON’ы для карт, и стор мог весить десятки мегабайт с нулевым влиянием на производительность. Итог – Redux Toolkit + RTK Query мой выбор в 2025 году.

Стили и компоненты. Самую первую версию MVP я начал строить с помощью Ant Design, китайской библиотеки компонентов. У них огромное множество различных компонентов на любой вкус, в том числе очень сложных. Однако приложение начало выглядеть как-то по-китайски, и я решил сделать что-то сам на базе Tailwind. Делать что-то сам я быстро устал, и нашел популярную коллекцию компонентов Shadcn, которую использует сейчас каждый второй западный стартап. Получилось незамысловато, но неплохо, темная тема заработала практически из коробки. Для сайта проекта я использовал платные компоненты, которые купил на shadcnblocks.

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

Бекэнд

Вдохновившись видео на ютубе от ребят из YCombinator, я хотел запустить все очень быстро, используя сторонние сервисы для решения любых задач, которые можно было решить на стороне. Само бэкенд-приложение представляло собой управляемое (managed) NodeJS + ExpressJS приложение на DigitalOcean App Platform, и все. Для аутентификации я использовал популярный сервис Auth0, а для сторонней базы данных – SaaS сервис MongoDB Atlas, который мне стоил денег.

Я осознал свою ошибку, когда начал общаться с пользователями и делать демо. Управление продуктами и проектами – это критически важный элемент процесса разработки, и людям очень важно знать, что их данные в безопасности и они их контролируют полностью. Когда разработчик-одиночка предлагает вам перенести важные процессы компании на его непонятные сервера, причем неизвестно, что с ним случится завтра – это не внушает доверия. Я понял, что фокусом развития должна быть коробочная self-hosted версия, которую можно быстро развернуть на любом сервере с помощью одного скрипта, подключить домен и наслаждаться высокой скоростью работы.

С точки зрения архитектуры мой бекэнд – это куча простых CRUD эндпоинтов, организованных тематически по доменам. Никакой оркестрации, никакой Кафки или состояния, просто перекладывание джейсонов в MongoDB туда и обратно с небольшой трансформацией с фокусом на то, чтобы максимально снизить количество запросов от клиента. Еще одним плюсом NodeJS + ExpressJS является то, что валидацию входящих запросов можно делать с помощью библиотеки Yup, которую я использую и на валидации форм на фронтенде.

Как я говорил выше, для этого приложения очень важна скорость перекладывания джейсонов, что отражается на скорости загрузки тикетов на фронтенде. Я анализировал скорость зарубежных и российских конкурентов по количеству и скорости выполнения запросов, и к моему удивлению, не все они очень старались сделать свои приложения очень быстрыми или сократить количество необходимых запросов. Некоторые российские системы управления проектами грузили тикеты по 300-400 миллисекунд. Здесь я хочу отметить российский проект YouGile – их приложение грузит тикет за 50-60 миллисекунд. В моем случае сочетание отсутствия массированной трансформации джейсонов, использование MongoDB и деплой приложения в московское Яндекс.Облако позволило довести скорость загрузки тикета до 30-40 миллисекунд при запросах из Москвы. С учетом фокуса на self-hosted версию, каждый пользователь может развернуть инстанс моего сервиса в наиболее близком к себе регионе и получить очень большую скорость работы. UX очень важен.

Однако переделка приложения с зависимостей от MongoDB Atlas и Auth0 заняла у меня пару месяцев (я работаю над проектом после основной работы по вечерам и выходным), и если перевести базу данных с MongoDB Atlas на локальный Docker инстанс той же базы было делом одного вечера, то с аутентификацией пришлось попотеть.

Аутентификация

Давным давно я делал собственные iOS приложения на Swift с использованием Firebase, чтобы не писать для них отдельный бекенд. Firebase SDK включает в себя аутентификацию, поэтому я обратился в первую очередь к Supabase, open source версии Firebase. Я много прочитал о поддержке self-hosted в Supabase и пришел к выводу, что я не уверен в надежности их решения. Коробочная версия не выглядела как приоритет для Supabase и она не получала такого внимания, как SaaS версия. Я начал искать решение, которое бы 1) было ориентировано на деплой на своем сервере 2) поддерживало NodeJS как first class citizen. Так я наткнулся на молодой стартап Supertokens, в который инвестировал YCombinator. Он представляет собой отдельное серверное приложение с Postgres базой. Главное бекенд-приложение общается с сервером Supertokens для JWT аутентификации, создания и изменения пользователей. Возможно, я столкнусь с проблемами, когда дойду до SSO аутентификации, но на текущем этапе все это работает как часы.

Фронтенд часть аутентификации реализована через собственный SDK, ориентирована на React и максимально проста, еще проще, чем Auth0.

Деплой

Во всех стартапах, где я работал, были отдельные админы, а затем девопсы, которые отвечали за CI/CD, поэтому у меня не было большого опыта написания конфигураций для Docker или Kubernetes с нуля. Во второй фазе моего проекта, когда я переносил все из сторонних сервисов в единый деплоймент, мне пришлось выучить оба, и в этом мне огромную помощь оказал Cursor с моделью Claude Sonnet 3.7. Я достаточно скептически отношусь к вайб-кодингу и в основном использую курсор для весьма умного автокомплита, но в деле написания yaml’ов для Docker и Helm шаблонов для Kubernetes Cursor сэкономил мне многие десятки часов времени чтения документации. В итоге с почти нулевого уровня знаний я смог поднять рабочий managed Kubernetes кластер на DigitalOcean за несколько дней, и весь процесс требовал только одного запуска Helm скрипта.

Проблемы начались, когда я увидел, что managed Kubernetes на Яндекс.Облаке стоит в два раза дороже, чем на DigitalOcean. Когда же я попытался завести кластер с помощью своего Helm скрипта на обычной VM, ничего не получилось. Я подумал и решил, что в моем случае Kubernetes особо и не нужен. Структура проекта простая, всего пять Docker образов и реверс-прокси, и zero downtime мне не нужен.

После этого вместе с Сursor я очень быстро создал интерактивный bash скрипт, который устанавливает на любую виртуальную машину Docker, скачивает нужные образы, заводит их, ставит реверс-прокси и SSL сертификаты. На зарубежных VM установка занимает 5 минут, на Яндекс.Облаке - 10 минут (там образы скачиваются с DockerHub почему-то в три раза медленнее).

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

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

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

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


  1. nihil-pro
    03.06.2025 05:21

    в mobx есть пять взаимозаменяемых способов диспатчить action creator’ы

    Это называется — не смотрел, но осуждаю. Action creator-ы это концепция из redux, а в mobx этого вообще нет.

    В redux есть проблемы и с производительностью, и с потреблением памяти. Он уступает в этом всем.

    Вообще грустно, что кто-то все еще рекомендует использовать redux.


    1. aerlinn13 Автор
      03.06.2025 05:21

      Я работаю с Redux семь лет, и ни разу не сталкивался с проблемами с производительностью в нем. Больше того, я привел пример с огромными геоджейсонами, которые не влияли на производительность никоим образом. Если вы сталкивались, прошу привести конкретные примеры ваших юзкейсов. В статье, которую вы приводите, в частности, потребление памяти – у всех плюс минус одно и то же.

      В частности, в вашем примере mobx в некоторых кейсах по потреблению памяти проигрывает.

      Дополнительно посмотрел сайт mobx, и сейчас диспатчить экшены можно шестью разными способами: https://mobx.js.org/actions.html. В чем разница, и зачем нужно шесть вариантов, конкретно на этой странице не объясняется.


      1. nihil-pro
        03.06.2025 05:21

        Даже на обрезанной картинке которую вы сюда вставили видно, что по показателю run memory redux потребляет на 38% больше памяти чем mobx, и на 50% больше kr-observable.
        Кстати, есть какое-то объяснение тому, почему вы обрезали картинку?

        диспатчить экшены можно шестью разными способами: https://mobx.js.org/actions.html

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

        1. By default, it is not allowed to change the state outside of actions. This helps to clearly identify in your code base where the state updates happen.

        Их всего три:

        1. action – можно вызвать просто как функцию, или использовать в качестве декоратора для метода в объекте. Его производное action.bound это только декоратор который дополнительно привязывает контекс.

        2. runInAction – то же самое что action, только ... create a temporary action that is immediately invoked. Can be useful in asynchronous processes.

        3. transaction – низкоуровневый API. Used to batch a bunch of updates without running any reactions until the end of the transaction. 

        Расскажите про остальные три экшн диспатчеры?


        1. aerlinn13 Автор
          03.06.2025 05:21

          Вы знаете, я не специалист по kr-observable, и не рассматриваю его в данной статье. Если для вас интересны бенчмарки, то давайте посмотрим на картинку еще раз. First paint – mobx 272.4, react redux hooks - 208.7. React redux immutable 285. Какие выводы, которые имеют значение на практике, вы можете сделать из этих данных? Сталкивались ли вы конкретно в своей практике со случаями низкой производительности Redux?

          Что касается экшенов.

          Благодарю вас за объяснение, но из документации непонятно, почему в этой табличке шесть вкладок. Как разработчик, я не хочу тратить большое количество времени на чтение документации, и потом узнавать на Хабре, что на самом деле только три вкладки означают варианты экшн диспатча. Как разработчик, я хочу один предсказуемый способ отправки экшна, о котором я могу рассказать других разработчикам. Я не хочу спорить с другими разработчиками, которые сделали это другим способом, потому что он тоже есть документации, что предсказуемость и читаемость кода важнее, чем то, что вам захотелось это сделать способом номер 2, потому что вам показалось это круче или вы написали на одну строчку кода меньше.


          1. mayorovp
            03.06.2025 05:21

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

            Вы так пишете, как будто в redux нет нескольких разных способов сделать одно и то же. Я могу вам перечислить пять способов диспатчнуть action (всего на 1 способ меньше чем в mobx):

            • connect с использованием mapDispatchToProps в форме объекта

            • connect с использованием mapDispatchToProps в форме функции

            • useDispatch

            • useStore + dispatch

            • обращение к глобальной переменной стора + dispatch (да, этот способ неправильный, но он есть!)

            Как же вы не путаетесь в этих способах-то?


            1. aerlinn13 Автор
              03.06.2025 05:21

              То, что вы перечислили – это API Redux образца 2018 года. Если вы взглянете на документацию Redux Toolkit, то ничего из перечисленного вы не увидите. Я лично использую Redux Toolkit уже пять лет на работе и в личных проектах. Очень рекомендую взглянуть.


              1. mayorovp
                03.06.2025 05:21

                Что-то я не вижу в API Redux Toolkit вообще ничего что касалось бы диспатча экшенов. Он ограничивается их созданием и всё, дальше нужен обычный dispatch из redux.

                А как раз его-то и можно вызвать 5 разными способами, перечисленными выше.


                1. aerlinn13 Автор
                  03.06.2025 05:21

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

                  А вот как он используется в компоненте.

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


                  1. mayorovp
                    03.06.2025 05:21

                    Судя по документации на createSlice, openModal - это action creator, он просто возвращает значение, которое требуется передать в dispatch. Сам по себе он ничего не сделает.

                    Как это вообще работает? Где можно это увидеть?

                    В их Quick Start используется самый обычный useDispatch из react-redux, который частью Redux Toolkit не является. А значит., и остальные 4 способа тоже рабочие, и вам всё ещё требуется выбрать один из них.


                    1. aerlinn13 Автор
                      03.06.2025 05:21

                      да, action creator, а что он еще должен делать кроме передачи данных? Изменением state занимается reducer.

                      Я немного перепутал с api файлом, у меня есть еще кастомный хук, в котором я держу инфраструктуру Redux Toolkit по модалам.

                      Я создал его один раз и использую по всему коду. Равно как и глобальный стор я создаю один раз и при создании нового слайса просто добавляю две строчки к глобальному стору.

                      Вы видите остальные 4 способа в документации Redux Toolkit? Допустим, если я не работал с этой библиотекой, я открываю документацию и вижу один способ. Зачем мне выбирать из 4 способов, о которых я не знаю и о которых там не пишут?


                      1. nihil-pro
                        03.06.2025 05:21

                        То есть, вместо условного:

                        toggleModalVisibility() {
                          this.visible = !this.visible
                        }

                        Вы выбрали cоздать кастомный хук, в котором:

                        1. Вызываете функцию useDispatch();

                        2. Вызываете функцию useSelector();

                        3. Создаете функцию openModal, в которой вызываете функцию dispatch, в которой передаете функцию open в которую передаете какой-то объект modal;

                        4. Создаете функцию closeModal, в которой вызываете функцию dispatch, в которую передаете функцию close.

                        5. На выходе создаете новый объект (массив скорее всего), куда кладете все это добро.

                        И это на каждый рендер. А ведь тут еще нет кода редьюсеров...

                        P.s. Извините что вклинился в ваш диалог. Виноват, не сдержался.


                      1. aerlinn13 Автор
                        03.06.2025 05:21

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

                        На самом деле, конкретно этот слайс чуть ли не единственный в моем коде, потому что 95% всей остальной Redux логики управляет Redux Toolkit Query.

                        Выглядит оно так:

                        а используется вот так:

                        Никаких useDispatch. Прямой импорт.


                      1. mayorovp
                        03.06.2025 05:21

                        Никаких useDispatch.

                        Почему вы свой хук-то не учитываете?


                      1. aerlinn13 Автор
                        03.06.2025 05:21

                        руками хук я создал только для модалов. useLazyGetWorkspaceQuery хук сгенерирован автоматически в апи файле.

                        Это автоматическая генерация.


                      1. mayorovp
                        03.06.2025 05:21

                        Хорошо, пусть автоматическая. Но это же означает, что появился шестой способ диспатча экшена - тот самый, которого не хватало для полного паритета с mobx! :-)

                        Причём вы как-то умудряетесь не путаться, когда нужно использовать createApi, а когда useDispatch. Так почему же сделать аналогичный выбор в mobx так сложно?


                      1. mayorovp
                        03.06.2025 05:21

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

                        Нет, вы не видите ни одного способа. Не путайте документацию и гайд по быстрому старту.

                        Вот если бы вы написали что у mobx нет нормального гайда по быстрому старту - я бы с вами согласился.


                      1. aerlinn13 Автор
                        03.06.2025 05:21

                        Не совсем понял, что вы имеете в виду. Я открываю mobx и вижу 6 вкладок на странице с описанием экшна. Tuturial или Getting Started там нет. Я открываю сайт Redux Toolkit и вижу несколько tutorial, в которых мне показывается один способ диспатча экшна через slice.


                      1. mayorovp
                        03.06.2025 05:21

                        tutorial - это не документация


          1. nihil-pro
            03.06.2025 05:21

            Давайте:

            First paint – mobx 272.4, react redux hooks - 208.7. React redux immutable 285

            Вопрос: есть redux, redux-hooks, redux-rematch, redux-hooks-immutable. Не подскажете что это? Зачем? Как вы своим разработчикам объясняете что есть что?

            Мой ответ такой: redux настолько плох, что хватается за любую возможность хоть как-то поправить свое положение, а redux-hooks апофеоз всего этого. Он настолько плох, что им приходится использовать хуки, чтобы хоть как-то нормально работать. Но зачем сравнивать хуки и redux или хуки и mobx? Это же сравнение теплого с мягким.

            Приложение чисто на хуках будет и быстрее и производительнее как любого из зоопарка решений redux, так и мобх. Вы выбираете redux, потому что вам нужен state-management а хуки с этим справляются плохо. Правильное сравнение это redux vs mobx.

            Вы также упомянули что redux когда-то и redux toolkit сейчас это не одно и тоже, давайте посмотрим:

            Счетчик на redux toolkit:

            import React from 'react'
            import { createRoot } from 'react-dom/client'
            import { configureStore, createSlice } from '@reduxjs/toolkit'
            import { Provider, useSelector, useDispatch } from 'react-redux'
            import type { PayloadAction } from '@reduxjs/toolkit'
            
            // Infer the `RootState` and `AppDispatch` types from the store itself
            type RootState = ReturnType<typeof store.getState>
            // Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
            type AppDispatch = typeof store.dispatch
            
            
            interface CounterState {
              value: number
            }
            
            const initialState: CounterState = {
              value: 0,
            }
            
            const counterSlice = createSlice({
              name: 'counter',
              initialState,
              reducers: {
                increment: (state) => {
                  state.value += 1
                },
                decrement: (state) => {
                  state.value -= 1
                }
              },
            })
            
            export const store = configureStore({
              reducer: counterSlice.reducer,
            })
            
            const { increment, decrement } = counterSlice.actions
            
            
            
            function Counter() {
              const count = useSelector((state: RootState) => state.counter.value)
              const dispatch = useDispatch()
            
              return (
                <div>
                  <div>
                    <button onClick={() => dispatch(increment())}>
                      Increment
                    </button>
                    <span>{count}</span>
                    <button onClick={() => dispatch(decrement())}>
                      Decrement
                    </button>
                  </div>
                </div>
              )
            }
            
            
            const container = document.getElementById('root')
            const root = createRoot(container)
            root.render(
              <Provider store={store}>
                <Counter />
              </Provider>,
            )  

            Как вы там написали?

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

            Да без проблем, держите MobX:

            import React from 'react'
            import { createRoot } from 'react-dom/client'
            import { makeAutoObservable } from 'mobx'
            import { observer } from 'mobx-react-lite'
            
            const state = makeAutoObservable({ count: 0 });
            
            const Counter = observer(() {
              return (
                <div>
                  <div>
                    <button onClick={() => ++state.count}>
                      Increment
                    </button>
                    <span>{count}</span>
                    <button onClick={() => --state.count}>
                      Decrement
                    </button>
                  </div>
                </div>
              )
            })
            
            const container = document.getElementById('root')
            const root = createRoot(container)
            root.render(<Counter />)  

            или kr-observable:

            import React from 'react'
            import { createRoot } from 'react-dom/client'
            import { makeObservable } from 'kr-observable'
            import { observer } from 'kr-observable/react'
            
            const state = makeObservable({ 
              count: 0,
              increase: () => ++this.count,
              decrease: () => --this.count,
            })
            
            const Counter = observer(() {
              return (
                <div>
                  <button onClick={state.decrease}>
                    Decrement
                  </button>
                  <div>{state.count}</div>
                  <button onClick={state.increase}>
                    Increment
                  </button>
                </div>
              )
            })
            
            const container = document.getElementById('root')
            const root = createRoot(container)
            root.render(<Counter />) 

            Какой из трех предложенных вариантов будет сложнее объяснять и поддерживать?

            Сталкивались ли вы конкретно в своей практике со случаями низкой производительности Redux?

            Да.


            1. aerlinn13 Автор
              03.06.2025 05:21

              давайте опустим kr-observable, если вы не против. Как я говорил, я не специалист в этой библиотеке.

              Если вы сталкивались со случаями низкой производительности Redux, прошу Вас поделиться опытом, что за тип приложения вы разрабатывали, как именно проявлялась деградация производительности, что вы предпринимали для того, чтобы понять корневую причину проблемы? Для сообщества Хабра это было бы ценной информацией.

              В ваших примерах со счетчиками – Redux Toolkit имеет несколько дополнительных строк кода по сравнению с Mobx. Это нормально. Во-первых, количество строк кода не равно простоте его поддерживания. Вы можете написать совершенно кривой и неподдерживаемый код на 10 строках, и иметь простой и ясный код на 15 строках.

              Больше того, примеры со счетчиками я не считаю корректными в принципе, потому что они не имеют тех проблем, которые имеют коммерческие продукты. Несколько человек не работают над счетчиками одновременно, счетчики не содержат сложной оркестрации HTTPS запросов, их данные не должны переиспользоваться в разных местах. Счетчикам в принципе не нужен state management, им достаточно локального useState.

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


              1. nihil-pro
                03.06.2025 05:21

                Для сообщества Хабра это было бы ценной информацией

                Слабая манипуляция.

                Во-первых, количество строк кода не равно простоте его поддерживания.

                Одно прямо следует из другого.

                Вы можете написать совершенно кривой и неподдерживаемый код на 10 строках, и иметь простой и ясный код на 15 строках.

                В примерах выше, реализация счетчика на mobx – 28 строк кода. Просто, лаконично, понятно. Не требует никаких объяснений. С redux toolkit кода ровно в два раза больше, и каждая новая строчка хуже предыдущей. Что это?

                () => dispatch(increment())

                а это?

                useSelector((state: RootState) => state.counter.value)

                а это?

                createSlice({
                  name: 'counter',
                  initialState,
                  reducers: {
                    increment: (state) => {
                      state.value += 1
                    },
                    decrement: (state) => {
                      state.value -= 1
                    }
                  },
                })

                сложной оркестрации HTTPS запросов, их данные не должны переиспользоваться в разных местах

                Покажете как redux решает такое, приведете пример?

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

                Очередная попытка в манипуляцию. Уверен, вы прекрасно понимаете, что NDA не позволяет никому привести такой пример.


                1. aerlinn13 Автор
                  03.06.2025 05:21

                  Я вам привел два примера из личного опыта. Один – приложение, основанное на рендеринге оверлеев для карт, которые рендерятся из огромных геоджейсонов, сидящих в Redux store. Второй – корпоративный мессенджер, в котором хранилось большое количество сообщений по чатам в этом самом Redux. Никаких проблем Redux не вызывал при тысячах сообщений в сторе. Для мессенджеров главная проблема – виртуализация bottom-up листов, т.е. чатов.

                  Вы строите свою аргументацию на том, что в счетчике mobx занимает меньше строк кода. Я с вами согласен. Означает ли меньшее количество строк кода простоту архитектуры – совершенно необязательно. Моя главная претензия к Mobx - это множество разных способов делать одно и то же. Для проектов, на которых работают несколько разработчиков, а также для стартапов, предсказуемость и читаемость кода я считаю важнее нескольких методов диспатча экшна, которые я могу выбрать по своему настроению. Если вам нравится выбирать по настроению – дело ваше. Я строю финтех решения на основе Redux Toolkit, а также сделал платформу для управления проектами на Redux Toolkit, и делюсь опытом с сообществом.


                  1. nihil-pro
                    03.06.2025 05:21

                    предсказуемость и читаемость кода я считаю важнее

                    А где она? Разве это () => dispatch(increment()) предсказуемость?

                    давайте опустим kr-observable, если вы не против.

                    Я то не против. Но вы тут предлагаете использовать редакс как хорошее решение, но когда я предложил kr-observable вам его рассматривать не хочется)).


                    1. aerlinn13 Автор
                      03.06.2025 05:21

                      Я думаю, что () => dispatch(increment()) это предсказуемость. Что именно в этой команде вам кажется непредсказуемым?

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

                      kr-observable, судя по всему, ваша авторская разработка, и я пока в коммерческой разработке не сталкивался с ней, поэтому судить не могу.


                      1. nihil-pro
                        03.06.2025 05:21

                        Я думаю, что () => dispatch(increment()) это предсказуемость. 

                        А я думаю, что предсказуемость выглядит так:

                         () => state.count += 1;


      1. mayorovp
        03.06.2025 05:21

        Дополнительно посмотрел сайт mobx, и сейчас диспатчить экшены можно шестью разными способами: https://mobx.js.org/actions.html.

        Выбираете тот, который удобен лично вам и используете его, разницы нет. В чём проблема-то, блин?

        В чем разница, и зачем нужно шесть вариантов, конкретно на этой странице не объясняется.

        А что тут объяснять-то?

        @action - исторически первый способ, который и предполагался основным
        makeObservable появился потому что комитет tc39 тянул с декораторами 10 лет
        makeAutoObservable нужен тем, кто считает что неявные соглашения лучше явного бойлерплейта
        action.bound - это модификатор к прошлым способам, который нужен чтобы обработчики событий не теряли this
        action(fn) - для тех кто любит писать, э-э-э, функциональненько
        runInAction - для однократного вызова


        1. aerlinn13 Автор
          03.06.2025 05:21

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

          Моя статья посвящена скоростной разработке масштабируемого MVP для веб-стартапа. Мне был нужен один эффективный, быстрый и предсказуемый способ работы со state, и мой выбор за Redux Toolkit.


  1. js2me
    03.06.2025 05:21

    Брать в 2025 году Redux как стейт менеджер зачем? Зачем нужны эти палки в колёса? Очень много раз уже обговаривалось огромное количество проблем и трудностей в разработке, которые даёт Redux

    mobx есть пять взаимозаменяемых способов диспатчить action creator’ы.

    Нет такого в MobX, на MobX можно написать Redux, возможно этого и пытались добиться созданием action creator`ов


    1. aerlinn13 Автор
      03.06.2025 05:21

      Redux в 2018 году и Redux Toolkit в 2025 это две большие разницы, я очень рекомендую взглянуть на документацию Redux Toolkit. За эти семь лет я работал в различных стартапах и корпорациях, где почти повсеместно использовался Redux, без каких-либо проблем и трудностей. В том числе в сферах, где производительность важна – в корпоративном мессенджере, например. Если вы сталкивались с проблемами с производительностью в Redux – прошу вас поделиться опытом.

      За экшн криэйторы мне уже выше пояснили – да, я неправильно применил термин, потому что большую часть времени работаю с Redux. Сути, однако, это не меняет – зачем нужно шесть вариантов одного и того же действия, никто не объясняет. Мне, как разработчику и человеку, который обучает других разработчиков, в первую очередь важна простота использования, расширения, рефакторинга при применении библиотеки, и меньше возможности накосячить. Если что-то может быть достигнуто, то я предпочитаю один предсказуемый путь, который я могу написать с закрытыми глазами, а не шесть, и потом не спорить с разработчиками о том, почему это сделано способом номер 3, а не номер 5, которые все есть в документации.


      1. strannik_k
        03.06.2025 05:21

        Я за последние пару лет побывал на паре проектов с Redux Toolkit (в одном из них использовали Redux Toolkit Query), а лет 5 назад был на проекте с Redux. В одном проекте с Redux Toolkit стали переходить на Mobx, а в другом на Zustand. В обоих случаях мне говорили, что код стал сильно понятней и стало проще разрабатывать.

        В 2025 году Redux Toolkit не имеет никакого бойлерплейта и прост, как пять копеек.

        Redux в 2018 году и Redux Toolkit в 2025 это две большие разницы.

        Разница большая, но особо лучше не стало. Одни недостатки сменились на другие. Гибкость низкая. Стал сложнее. Все ещё на любой чих приходиться писать много кода в избыточном количестве файлов. По-прежнему вместо вызова методов диспатчат экшены. Для типовых задач используется своеобразное api, далекое от нативного.

        Проблемно и неочевидно вынести дублирующиеся в разных слайсах редьюсеры, части стейта. Я как-то спросил в большом корпоративном фронтенд чате, как это лучше сделать. Получил с десяток рекомендаций сменить стейт менеджер)
        На эту тему пишут целые статье вроде этой: https://habr.com/ru/articles/771660/
        А в официальной документации предлагают такое:
        https://redux-toolkit.js.org/usage/usage-with-typescript#wrapping-createslice
        This can be done with createSlice as well, but due to the complexity of the types for createSlice, you have to use the SliceCaseReducers and ValidateSliceCaseReducers types in a very specific way.
        А ведь в слайсах могут понадобиться не только стейт и редьюсеры, но и селекторы с extraReducers.

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

        Автору статьи и тем, кто всё еще пишет на Redux Toolkit рекомендую для начала попробовать Zustand. Это популярный редаксо-подобный более простой стейт менеджер с меньшим количеством бойлерплейта. После него Redux Toolkit большинству перестает казаться хорошим решением) Но Zustand не избавляет от бойлерплейта селекторов в компонентах. Чтобы избавиться еще и от этого бойлерплейта можно воспользоваться react-tracked. Получиться ближе к более читаемому и гибкому коду, получаемому при использовании таких стейт менеджеров как mobx, valtio. Ну и если нужен аналог Redux Toolkit Query, то есть React-Query.


  1. kubk
    03.06.2025 05:21

    Зачем несколько сторов, если достаточно одного?

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


    1. aerlinn13 Автор
      03.06.2025 05:21

      а если стор пустой и туда ничего не загружено, и не будет загружено, пока не подгружены компоненты, которые с ним работают, какое это имеет значение?

      При большом желании, однако, RTK Query поддерживает lazy loading https://redux-toolkit.js.org/rtk-query/usage/code-splitting.


      1. mayorovp
        03.06.2025 05:21

        а если стор пустой и туда ничего не загружено, и не будет загружено, пока не подгружены компоненты, которые с ним работают, какое это имеет значение?

        А при чём тут состояние стора в рантайме, когда речь шла об оптимизации размера бандла?

        При большом желании, однако, RTK Query поддерживает lazy loading

        Это именно что при большом желании. А несколько "сторов" (которые вообще не сторы) позволяют легко достичь того же самого просто по умолчанию.