Как стать автором
Обновить

Проверенный стек технологий для быстрого создания Web SaaS в 2025 году

Уровень сложностиПростой
Время на прочтение7 мин
Количество просмотров1.2K
Всего голосов 7: ↑4 и ↓3+2
Комментарии33

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

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

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

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

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

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

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

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

Даже на обрезанной картинке которую вы сюда вставили видно, что по показателю 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. 

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

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

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

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

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

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

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

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

  • useDispatch

  • useStore + dispatch

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Никаких useDispatch.

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

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

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

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

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

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

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

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

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

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

Давайте:

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?

Да.

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

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

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

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

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

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

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

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

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

Вы можете написать совершенно кривой и неподдерживаемый код на 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 не позволяет никому привести такой пример.

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

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

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

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

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

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

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

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

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

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

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

 () => state.count += 1;

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

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

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

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

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

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

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

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

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

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

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

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

Я за последние пару лет побывал на паре проектов с 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.

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

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

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

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

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

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

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

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

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации