Две проблемы React-разработчика

У React-разработчика две беды:

  • Целевые данные изменились, а ререндера не произошло.

  • Целевые данные не изменились, а ререндер произошёл.

Есть много способов подружить данные с UI. В рамках этой статьи я пройдусь по популярным подходам: разберём, как устроены подписки, почему происходят лишние ререндеры, и когда какой инструмент подходит лучше. Выбор библиотек основан только на личном опыте: описал то, с чем сталкивался лично. В целом, все библиотеки из списка крайне популярны в React сообществе, просто если вашей любимой библиотеки здесь нет - значит я еще с ней не работал, буду рад рекомендациям.

Итак, как мы знаем, React по своей сути не реактивен (у разработчиков было чувство юмора), то есть при изменении переменной интерфейс не обновляется автоматически. UI обновляется тогда, когда React видит изменение через state/props/context или через механизм внешней подписки (например, useSyncExternalStore).

Перейдем к обзору существующих подходов:

Встроенные решения: useState/useReducer + Context

Когда выбирать

  • Локальный UI state одного компонента / виджета: формы, табы, другая ui мелочь.

  • Shared state внутри одного поддерева: несколько компонентов должны видеть и менять одно состояние.

useState / useReducer

useState и useReducer дают простой контракт: обновился state → React планирует обновление → компонент rerender.

Атомарность по умолчанию — компонентная: изменился state → rerender всего компонента (дочерние можно оптимизировать через React.memo или другие паттерны).

Context

Context решает проблему prop drilling, но имеет важное свойство:

  • все консьюмеры ререндерятся при смене value у Provider, если ссылка на value изменилась (===).

Самый частый источник лишних ререндеров — это не “плохой Context”, а то, что value создаётся заново на каждый Provider.

Базовый паттерн таков:

  • Provider хранит state (useState/useReducer)

  • value мемоизируем через useMemo

Пример (Context + useState)

// file: todos-context.tsx
import React, { createContext, useContext, useMemo, useState, ReactNode } from 'react';

export interface Todo {
  id: number;
  text: string;
  completed: boolean;
}

interface TodosContextValue {
  todos: Todo[];
  addTodo: (text: string) => void;
  toggleTodo: (id: number) => void;
}

const TodosContext = createContext<TodosContextValue | null>(null);

export function TodosProvider({ children }: { children: ReactNode }) {
  const [todos, setTodos] = useState<Todo[]>([]);

  const addTodo = (text: string) => {
    setTodos((prev) => [...prev, { id: Date.now(), text, completed: false }]);
  };

  const toggleTodo = (id: number) => {
    setTodos((prev) =>
      prev.map((t) => (t.id === id ? { ...t, completed: !t.completed } : t))
    );
  };

  const value = useMemo<TodosContextValue>(
    () => ({ todos, addTodo, toggleTodo }),
    [todos]
  );

  return <TodosContext.Provider value={value}>{children}</TodosContext.Provider>;
}

export function useTodos() {
  const ctx = useContext(TodosContext);
  if (!ctx) throw new Error('useTodos must be used within TodosProvider');
  return ctx;
}
// file: TodoList.tsx
import React from 'react';
import { useTodos } from './todos-context';

export function TodoList() {
  const { todos, addTodo, toggleTodo } = useTodos();

  return (
    <div>
      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>
            <label>
              <input
                type="checkbox"
                checked={todo.completed}
                onChange={() => toggleTodo(todo.id)}
              />
              {todo.text}
            </label>
          </li>
        ))}
      </ul>

      <button onClick={() => addTodo('Новая задача')}>Добавить</button>
    </div>
  );
}
// file: App.tsx
import React from 'react';
import { TodosProvider } from './todos-context';
import { TodoList } from './TodoList';

export default function App() {
  return (
    <TodosProvider>
      <TodoList />
    </TodosProvider>
  );
}

Плюсы

  • 0 external dependencies.

  • Отлично для локального UI state.

  • Provider = естественная область жизни state (unmount → state сбросился).

Минусы

  • Context без селекторов: изменение value → rerender всех consumers.

  • В больших приложениях часто приходит необходимость в selectors/DevTools/middleware.

Лучше не использовать, если

  • Частые обновления (например, realtime) + много consumers.

  • Нужно удобно дебажить историю изменений (time travel, action log).

Библиотеки

Но если все работает из коробки, почему при поиске по npm.js запроса "react state manager" у нас 1000+ библиотек?

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

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

В React нет встроенной системы селекторов или вычисления зависимостей: либо вводить дробление контекстов, либо страдать от лишних рендеров. Также нет DevTools, показывающих изменения контекста (React DevTools умеет показывать текущее значение контекста, но не хронологию). Трассировать изменения через консоль или debugger сложнее, особенно если много компонентов писают в состояние. Нет и средств типа time-travel debugging.


Redux

Redux — централизованный иммутабельный store с однонаправленным потоком данных, обеспечивающий предсказуемость, воспроизводимость и мощные DevTools. Компоненты подписываются на конкретные срезы состояния через селекторы и обновляются только при изменении ссылок. Решение отлично масштабируется для больших команд и сложных доменов, но остаётся более многословным и часто избыточным для небольших проектов.

Когда выбирать

  • Большой продукт, много команд, много бизнес-событий.

  • Нужна строгая дисциплина изменения state, action log и DevTools.

  • Нужна предсказуемость: action -> reducer -> next state.

Redux — централизованный store + “one-way data flow”. В React обычно используется связка Redux Toolkit + React-Redux.

Ключевая вещь для ререндеров: React-Redux подписывает компоненты на store через selectors.

  • Компонент ререндерится, если результат useSelector(selector) изменился (по умолчанию сравнение значений, часто ===, плюс есть shallowEqual и кастомные сравнения).

Мини-диаграмма

UI event
  ↓
dispatch(action)
  ↓
reducer → nextState
  ↓
useSelector runs selectors
  ↓
if selected slice changed → rerender

Пример (Redux Toolkit)

// file: store.ts
import { configureStore, createSlice, PayloadAction } from '@reduxjs/toolkit';

export interface Todo {
  id: number;
  text: string;
  completed: boolean;
}

const todosSlice = createSlice({
  name: 'todos',
  initialState: [] as Todo[],
  reducers: {
    addTodo: (state, action: PayloadAction<string>) => {
      state.push({ id: Date.now(), text: action.payload, completed: false });
    },
    toggleTodo: (state, action: PayloadAction<number>) => {
      const todo = state.find((t) => t.id === action.payload);
      if (todo) todo.completed = !todo.completed;
    },
  },
});

export const { addTodo, toggleTodo } = todosSlice.actions;

export const store = configureStore({
  reducer: {
    todos: todosSlice.reducer,
  },
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
// file: TodoList.tsx
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from './store';
import { addTodo, toggleTodo } from './store';

export function TodoList() {
  const todos = useSelector((s: RootState) => s.todos);
  const dispatch = useDispatch<AppDispatch>();

  return (
    <div>
      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>
            <label>
              <input
                type="checkbox"
                checked={todo.completed}
                onChange={() => dispatch(toggleTodo(todo.id))}
              />
              {todo.text}
            </label>
          </li>
        ))}
      </ul>

      <button onClick={() => dispatch(addTodo('Новая задача'))}>Добавить</button>
    </div>
  );
}
// file: App.tsx
import React from 'react';
import { Provider } from 'react-redux';
import { store } from './store';
import { TodoList } from './TodoList';

export default function App() {
  return (
    <Provider store={store}>
      <TodoList />
    </Provider>
  );
}

Плюсы

  • Развитые DevTools, action log, time travel.

  • Стандартизированный подход: удобно для code review и масштабирования команды.

  • Selector модель позволяет контролировать атомарность каждого ререндера.

Минусы

  • Больше boilerplate и структуры (даже с Toolkit).

  • Для маленьких приложений может быть overkill.

Лучше не использовать, если

  • Маленький проект, локальный UI state — проще решить встроенными средствами.

  • Сильно интерактивные сценарии с частыми мелкими мутациями, где нужно просто менять объект.


Zustand

Zustand — лёгкий глобальный store на хуках с селекторной подпиской: компонент обновляется только при изменении выбранной части состояния. Библиотека почти не навязывает архитектуру, что делает её удобной и быстрой в разработке, но в больших командах требует дисциплины, иначе структура стора быстро становится неуправляемой.

Когда выбирать

  • Нужен global state, но без характерного для Redux boilerplate.

  • Хочется selector-based подписки и минимального API.

  • Проект небольшой / средний или команда готова устанавливать конвенции.

Zustand представляет собой легковесный store, который возвращает кастомный hook. Компоненты подписываются через selectors.

Важно писать селекторы так, чтобы они не возвращали новый объект каждый раз, иначе - лишние ререндеры.

Пример (selectors + shallow)

// file: useTodoStore.ts
import { create } from 'zustand';
import { shallow } from 'zustand/shallow';

export interface Todo {
  id: number;
  text: string;
  completed: boolean;
}

interface TodoState {
  todos: Todo[];
  addTodo: (text: string) => void;
  toggleTodo: (id: number) => void;
}

export const useTodoStore = create<TodoState>((set) => ({
  todos: [],
  addTodo: (text) =>
    set((s) => ({
      todos: [...s.todos, { id: Date.now(), text, completed: false }],
    })),
  toggleTodo: (id) =>
    set((s) => ({
      todos: s.todos.map((t) => (t.id === id ? { ...t, completed: !t.completed } : t)),
    })),
}));

export const useTodoActions = () =>
  useTodoStore(
    (s) => ({ addTodo: s.addTodo, toggleTodo: s.toggleTodo }),
    shallow
  );
// file: TodoList.tsx
import React from 'react';
import { useTodoStore, useTodoActions } from './useTodoStore';

export function TodoList() {
  const todos = useTodoStore((s) => s.todos);
  const { addTodo, toggleTodo } = useTodoActions();

  return (
    <div>
      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>
            <label>
              <input
                type="checkbox"
                checked={todo.completed}
                onChange={() => toggleTodo(todo.id)}
              />
              {todo.text}
            </label>
          </li>
        ))}
      </ul>

      <button onClick={() => addTodo('Новая задача')}>Добавить</button>
    </div>
  );
}

Плюсы

  • Минимализм и хороший DX.

  • Selector-based подписки → контроль rerender.

  • Можно держать несколько store (feature-based), не навязывает архитектуру.

Минусы

  • Архитектура на совести команды: без соглашений легко получить кашу.

  • Меньше готовой инфраструктуры, чем у Redux (middleware, conventions).

Лучше не использовать, если

  • Проект с жёсткими требованиями к аудиту изменений и единым процессом через actions.

  • Команда не готова договориться о naming / structure conventions.


Jotai

Jotai разбивает состояние на атомы — минимальные единицы подписки, включая вычисляемые атомы с графом зависимостей. Компоненты обновляются только при изменении используемых атомов, что даёт очень точечные ререндеры без ручных селекторов. Подход хорошо ложится на component-first архитектуру, но в больших приложениях усложняет управление связями между атомами.

Когда выбирать

  • Много независимых кусочков state и хочется hooks, но глобально.

  • Нужна минимальная единица подписки — atoms.

  • Много derived state: удобно собирать граф зависимостей.

Jotai предлагает atomic state: каждый atom — отдельная точка подписки. Обновление atom → rerender только тех компонентов, которые используют этот atom.

Пример

// file: atoms.ts
import { atom } from 'jotai';

export interface Todo {
  id: number;
  text: string;
  completed: boolean;
}

export const todosAtom = atom<Todo[]>([]);
// file: TodoList.tsx
import React from 'react';
import { useAtom } from 'jotai';
import { todosAtom } from './atoms';

export function TodoList() {
  const [todos, setTodos] = useAtom(todosAtom);

  const addTodo = () => {
    setTodos((prev) => [
      ...prev,
      { id: Date.now(), text: 'Новая задача', completed: false },
    ]);
  };

  const toggleTodo = (id: number) => {
    setTodos((prev) =>
      prev.map((t) => (t.id === id ? { ...t, completed: !t.completed } : t))
    );
  };

  return (
    <div>
      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>
            <label>
              <input
                type="checkbox"
                checked={todo.completed}
                onChange={() => toggleTodo(todo.id)}
              />
              {todo.text}
            </label>
          </li>
        ))}
      </ul>

      <button onClick={addTodo}>Добавить</button>
    </div>
  );
}

Плюсы

  • Fine-grained подписки на уровне atom.

  • Derived atoms помогают строить dependency graph.

  • Очень component-first API.

Минусы

  • В больших проектах нужно следить за структурой atom (naming, grouping, ownership).

  • Derived state легко превратить в сложный граф, если не контролировать.

Лучше не использовать, если

  • Если нужен единый action log и строгая процессность изменений (скорее Redux).


MobX

MobX реализует реактивную модель с наблюдаемыми объектами и прямыми мутациями состояния. Зависимости отслеживаются автоматически на уровне свойств, поэтому обновляются только реально затронутые компоненты. Это даёт высокую производительность и удобство в сложных зависимых состояниях, но снижает явность потока данных и усложняет отладку для React-разработчиков.

Когда выбирать

  • Много derived state и сложные зависимости.

  • Хочется писать мутациями (imperative updates), а UI обновлялся автоматически.

  • Нужна максимально fine-grained реактивность на уровне полей.

MobX — это dependency tracking: компоненты (через observer) автоматически “подписываются” на observables, которые они прочитали во время render.

Вместо “селектор → сравнение результата” здесь работает модель “прочитал поле → подписался на поле”.

Мини-диаграмма

render (observer component)
  ↓
reads observable fields → tracking deps
  ↓
mutation of observable field
  ↓
notify exact observers → rerender

Пример

// file: todoStore.ts
import { makeAutoObservable } from 'mobx';

export interface Todo {
  id: number;
  text: string;
  completed: boolean;
}

export class TodoStore {
  todos: Todo[] = [];

  constructor() {
    makeAutoObservable(this);
  }

  addTodo(text: string) {
    this.todos.push({ id: Date.now(), text, completed: false });
  }

  toggleTodo(id: number) {
    const todo = this.todos.find((t) => t.id === id);
    if (todo) todo.completed = !todo.completed;
  }
}

export const todoStore = new TodoStore();
// file: TodoList.tsx
import React from 'react';
import { observer } from 'mobx-react-lite';
import { todoStore } from './todoStore';

export const TodoList = observer(() => {
  const store = todoStore;

  return (
    <div>
      <ul>
        {store.todos.map((todo) => (
          <li key={todo.id}>
            <label>
              <input
                type="checkbox"
                checked={todo.completed}
                onChange={() => store.toggleTodo(todo.id)}
              />
              {todo.text}
            </label>
          </li>
        ))}
      </ul>

      <button onClick={() => store.addTodo('Новая задача')}>Добавить</button>
    </div>
  );
});

Плюсы

  • Очень точечные обновления благодаря dependency tracking.

  • Удобно для сложного derived state.

  • Меньше boilerplate: мутации напрямую.

Минусы

  • Нужно понимать, как формируются подписки (что именно ушло в render).

  • Debugging может быть непривычным после action-based систем.

Лучше не использовать, если

  • Команда хочет строгое, стандартизированное изменение state через actions.

  • Нужен простой mental model без реактивной магии.


Valtio

Valtio использует Proxy для реактивного состояния с прямыми мутациями и property-level подписками через useSnapshot. Компоненты перерисовываются только при изменении прочитанных свойств, что делает библиотеку удобной для highly-interactive UI. При этом состояние по умолчанию глобальное, экосистема менее зрелая, и изоляция экземпляров требует дополнительных паттернов.

Когда выбирать

  • Очень интерактивный UI с частыми мелкими мутациями (drag&drop, realtime dashboards, canvas-like интерфейсы).

  • Хочется писать state как обычный mutable object.

  • Нужны fine-grained обновления “по прочитанным полям”, но проще, чем MobX.

Valtio строится на Proxy. Компонент получает snapshot через useSnapshot(state) и автоматически подписывается на свойства, которые он прочитал во время render.

Пример

// file: state.ts
import { proxy } from 'valtio';

export interface Todo {
  id: number;
  text: string;
  completed: boolean;
}

export const state = proxy<{ todos: Todo[] }>({
  todos: [],
});

export function addTodo(text: string) {
  state.todos.push({ id: Date.now(), text, completed: false });
}

export function toggleTodo(id: number) {
  const todo = state.todos.find((t) => t.id === id);
  if (todo) todo.completed = !todo.completed;
}
// file: TodoList.tsx
import React from 'react';
import { useSnapshot } from 'valtio';
import { addTodo, state, toggleTodo } from './state';

export function TodoList() {
  const snap = useSnapshot(state);

  return (
    <div>
      <ul>
        {snap.todos.map((todo) => (
          <li key={todo.id}>
            <label>
              <input
                type="checkbox"
                checked={todo.completed}
                onChange={() => toggleTodo(todo.id)}
              />
              {todo.text}
            </label>
          </li>
        ))}
      </ul>

      <button onClick={() => addTodo('Новая задача')}>Добавить</button>
    </div>
  );
}

Плюсы

  • Очень простой mutable API.

  • Fine-grained обновления по property access tracking.

  • Хорошо ложится на highly-interactive UI.

Минусы

  • Меньше экосистемы и “стандарта” вокруг архитектуры.

  • По умолчанию proxy часто используется как singleton (export из модуля) — нужно помнить про scoping.

Лучше не использовать, если

  • Нужна строгая модульность/изоляция инстансов без дополнительных паттернов.

  • Большая команда без договорённостей о scoping и ownership state.


Сравнение подходов

Подход

Атомарность подписки

Boilerplate

Debug/DevTools

Team scaling

SSR/изоляция

useState/useReducer

компонент

минимальная

базовая

низкое

ок

Context

все consumers по value

минимальная

базовая

средне

oк (Provider per request)

Redux + React-Redux

selector slice

средняя

отличная

отличное

нужен per-request store

Zustand

selector slice

низкая

средняя

высокое

нужен per-request store/instance

Jotai

atom

низкая

средняя

высокое

нужен provider scoping

MobX

поле/observable

низкая

средняя

отличное

нужен per-request instance

Valtio

property access

низкая

базовая/средняя

высокое

нужен per-request instance


Выводы и чеклист

State management в React — это в первую очередь управление подписками и атомарностью обновлений, чтобы каждый компонент получал ровно столько данных, сколько ему нужно.

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

  • Локальный UI stateuseState / useReducer.

  • Shared state в одном поддеревеContext + useReducer/useState, но value обязательно через useMemo.

  • Нужны selectors и контроль rerender без большого boilerplate → Zustand.

  • Нужны стандарты, action log, DevTools, team scaling → Redux Toolkit + React-Redux.

  • Нужен atomic/fine-grained state и component-first стиль → Jotai.

  • Сложный derived state и dependency tracking → MobX.

  • Много мелких мутаций и highly-interactive UI → Valtio.

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

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

Периодически пишу и обсуждаю похожие темы в телеграм-канале.

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


  1. dominus_augustus
    12.01.2026 11:33

    Берите mobx и не мучайтесь


    1. globin_write Автор
      12.01.2026 11:33

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


      1. markelov69
        12.01.2026 11:33

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

        Как именно?

        state.count++;
        state.title = 'new title';

        Где мучения? Все кто следил за state.count и state.title автоматически перендерились.

        const myComp = observer(() => {
        return <div>count: {state.count} / title: {state.title}</div>
        })

        Тут как бы все максимально просто и без лишнего кода.


        1. globin_write Автор
          12.01.2026 11:33

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

          Фраза про "мучения" не должна быть воспринята буквально. То есть, все стейт менеджеры из статьи это решения, доказавшие свою эффективность на огромном числе проектов, redux и mobx особенно.

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

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

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


      1. dominus_augustus
        12.01.2026 11:33

        Тоже интересно, в чем были мучения, вроде все просто


        1. globin_write Автор
          12.01.2026 11:33

          Ответил выше)

          Интересно ваше мнение


    1. Elendiar1
      12.01.2026 11:33

      Пользуюсь rtk redux + query, потому что есть кодогенерация апи в стор. Как по мне - имба.

      Как с этим у mobx?


  1. DmitryKazakov8
    12.01.2026 11:33

    На Mobx можно писать так же, как в примере на Valtio, используя вместо proxy функцию observable. Единственное отличие в коде будет в том, что Mobx подключается к реактовым компонентам через враппинг в HOC observer, а Valtio - через useSnapshot. Плюсы Mobx подхода с враппингом в том, что можно использовать хоть 100 разных реактивных состояний в компоненте напрямую, без вызова для каждого useSnapshot, а сам враппинг можно сделать автоматически через бандлер, то есть компоненты в коде будут выглядеть чистыми.

    Вариант с классовыми сторами просто более удобен в плане DX, поэтому часто выбирают его (меньше импортов и констант, меньше коллизии имен, не важен порядок объявления методов и геттеров). Но можно писать и объектами


    1. globin_write Автор
      12.01.2026 11:33

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

      В Redux/Zustand перерисовка явно связана с вызовом setState или dispatch, что с ростом масштаба кратно упрощает дебаг. Плюс DevTools у Redux (с логом действий и diffами состояния) гораздо мощнее, чем у MobX.

      MobX очень гибок, у него этого не отнять. Но есть ощущение, что часто эта гибкость стреляет в ногу с ростом масштаба.

      Безусловно, я далек от экспертности в контексте таких эджкейсов MobX. Если у вас есть опыт, как такое реашется, поделитесь, пожалуйста.


      1. DmitryKazakov8
        12.01.2026 11:33

        В Mobx тоже перерисовка связана явно, только вместо dispatch с полным пересозданием структуры здесь просто мутация store.param = value. Это дает намного более удобный DX - можно в IDE посмотреть все места, где читается/изменяется конкретное свойство и работают быстрые переходы.

        А в Redux нужно сначала найти нужный initialState, потом найти десяток редюсеров, которые меняют в нем param, потом найти action name, по нему сделать полный поиск по проекту, чтобы найти все места где делается dispatch с этим названием экшена, не забыть пройтись по селекторам и connect HOC, чтобы узнать, где используются конкретный стор, а потом в каждом найденном компоненте искать где читается param. К сожалению, довольно много работал с Redux и пол-дня состояло как раз из этих действий, потому что никаких быстрых переходов или Find Usages у него не предусмотрено.

        И с Redux в итоге не так-то просто найти, "почему компонент перерендерился", потому что нужно смотреть в Dev Tools и вручную искать какой именно из диспатчей данных повлиял на конкретный компонент, проходя все этапы, описанные выше. Но да, в нем есть диффы состояний, которые немного могут упростить процесс.

        Дебажить кейс "почему компонент перерендерился" в целом непросто в любом подходе, и помогает тут скорее создание более атомарных компонентов, а не монстров с десятками состояний. Но с Mobx это как правило не нужно - компонент ререндерится при изменении читаемых свойств и если он перерендерился - значит нужно. Это не реактовый или редаксовый стейт, где все ререндерится даже если нечитаемые поля изменились, если тщательно не оптимизировать все через селекторы. Поэтому такой проблемы и не возникает


        1. globin_write Автор
          12.01.2026 11:33

          Это на самом деле очень хороший комментарий, спасибо за информацию!

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

          Я о том, что более структурированный и декларативный redux, по моему опыту, в случае подобного легаси был бы предпочтительнее, потому что "распутать" состояние в его случае как будто проще.

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


          1. DmitryKazakov8
            12.01.2026 11:33

            Я тоже работал в проектах, где на Mobx создавали абсолютно неподдерживаемого монстра, используя все возможные антипаттерны. Например, использовали автонормализацию snake_case -> camelCase при получении данных из сторонних источников, что ломало быстрые переходы и поиск по проекту. Делали огромные Rich Store на тысячи строк, неконтролируемые autorun во всех возможных местах, использовали одновременно реактивные состояния и useState/useEffect, причем пытаясь из их комбинации сделать computed. По структуре тоже была каша - и Entity Stores, и сервисы со своими сторами, и модульные, и глобальные в несколько уровней.

            Так что ваш аргумент тоже корректен - чем более гибок инструмент, тем легче писать некачественный код. Но вот еще один поинт - из Mobx очень легко сделать Redux. Просто добавить в ваш пример в статье метод dispatch(action, payload) , который будет делать Object.assign(this._state, payload) в мутабельном стиле (для оптимизации изменения глубоко вложенных структур придется чуть усложнить). Ну и switch-case туда. При этом рендеринг UI компонентов будет оптимизирован из коробки - что бы мы ни меняли, будут точечно ререндериться только те компоненты, которые читают конкретный параметр, и только когда он изменился.

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

            То есть никто не запрещает написать на Mobx флоу работы с данными как в Redux, описать это в тех. документации и на ревью проверять, что все разработчики придерживаются этого флоу. Либо не Redux-like, а выбрать удобный для проекта и команды способ установки-получения значений, ведь этих паттернов огромное количество, и Mobx с очень большой вероятностью сможет так работать. То есть он - это все остальные СТМ вместе взятые, остается только выбрать удобный подход и консистентно работать с ним.


      1. clerik_r
        12.01.2026 11:33

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

        Ну так он перерндерился только если данные были изменены. Что за странная "сложность понять"? Ну читает у вас компонент 50 реактивных полей, одно изменилось - он перерендерился. С чего вообще вы вдруг решили объявить рендер компонента проблемой?)) Это нормально. Более того, я не могу представить задачу даже с помощью натягивания совы на глобус, где проблема была бы именно в MobX, а не в вашем компоненте. Т.е. "задача" понять при изменении какого именно поля произошел перерендер изначально не имеет никакого смысла практического. Ну а если так сильно хочется, то и у MobX есть dev tools. Но я dev tools мобикс вообще не пользуюсь уже как 8 лет работая с ним каждый день.


        1. globin_write Автор
          12.01.2026 11:33

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

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

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


          1. akardapolov
            12.01.2026 11:33

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

            Хороши это конечно хорошо - но что удобнее в работе, производительнее и проч.

            Лично я по отзывам не стал связываться с Redux - и сразу (после небольшого research здесь) начал писать свои первые приложения React с использованием Mobx. И в общем-то на мой, ни разу не спеца в этой сфере, взгляд - получалось достаточно удобно (не спрашивайте почему - это на уровне ощущений, тем более давно прошедших, практически в до ChatGPT-ую эпоху).

            Мне в этом многообразии State Manager-ов для React кажется какой-то перебор (глядя из мира Java - где практически один стандарт для разработки Enterprise backend-a - Spring и 2-х систем сборки (Maven + Gradle)). Тут же - для SM 1000 либ. Это перебор КМВ. Тем более что есть жалобы от разработчиков на SM от создателей React на сложность и многословность (что первая, что вторая их версия) - вроде же Mobx должен решать проблемы, ну Zustand как вариант, да хотя бы тот же Redux.

            Просто интересно, что это за тема такая горячая (или перегретая) с SM в React?


            1. globin_write Автор
              12.01.2026 11:33

              Мне кажется, это что-то типо локального вида спорта у js комьюнити - плодить либы по поводу и без.

              Если серьезно, то по сути реальная разница есть между либами, которые мутируют объекты (mobx, valtio, ...) и теми, кто идёт по пути реакта и объекты не мутирует.

              А в рамках одной группы разница не так велика. Я, наверное, соберу ещё хейта за такие мысли, но разница буквально в предпочтительных паттернах и синтаксисе.

              Если прям интересно, очень рекомендую книгу "Micro state Management react hooks". Читается за день, даёт ответы на большинство вопросов связанных с см в контексте react приложений.


  1. HuginCode
    12.01.2026 11:33

    А чего про solid и recoil не написал? Без них список не полный как-то


    1. globin_write Автор
      12.01.2026 11:33

      А тут все просто, не использовал их еще)


  1. NikonSevast
    12.01.2026 11:33

    ожидал в каментах увидеть открытый портал в ад, но пока разочарован


    1. cmyser
      12.01.2026 11:33

      думаю ожидали фрейморк который нельзя называть и который даже лучше мобикса ?)


    1. globin_write Автор
      12.01.2026 11:33

      Делаю, что могу)))

      Знал, на что я шел


    1. nihil-pro
      12.01.2026 11:33

      _$<п04ему?


  1. ekkebeatovich
    12.01.2026 11:33

    в обзоре упущены reatom и effector


    1. markelov69
      12.01.2026 11:33

      в обзоре упущены reatom и effector

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


      1. globin_write Автор
        12.01.2026 11:33

        А чем хуже? По вашему мнению. Я с ними не работал, экспертизы тут не имею