Pull to refresh

Redux vs Mobx кого же выбрать для React-приложения в 2024 году?

Level of difficultyMedium
Reading time21 min
Views16K

Привет, Хабр!

Сегодня я хочу поделиться с вами своими размышлениями о том, какой стейт менеджер лучше использовать для разработки приложений на React в 2024 году. Как вы знаете, React — это одна из самых популярных и мощных библиотек для создания пользовательских интерфейсов, которая предоставляет множество возможностей и преимуществ для разработчиков. Однако, по мере роста и усложнения приложений на React, возникает необходимость в управлении состоянием и данными, которые используются в разных компонентах. Для этого существуют различные решения, называемые стейт менеджерами. Стейт менеджер — это инструмент, который позволяет централизованно хранить, обновлять и передавать данные между компонентами, а также реагировать на изменения состояния.

В этой статье я рассмотрю два из самых популярных и зрелых стейт менеджеров для React: Redux и Mobx, а также расширения mobx-state-tree и Redux Toolkit. Я сравню их основные принципы, преимущества и недостатки, а также покажу примеры их использования в коде. Также я попытаюсь ответить на вопрос, какой из них лучше подходит для разработки современных приложений на React в 2024 году.

Содержание

Redux

Redux — это стейт менеджер, основанный на концепции потока данных в одном направлении (unidirectional data flow). Это означает,....

А теперь простыми словами:

Redux  — это способ хранить и менять данные в приложении на React. Все данные собраны в одном месте, которое называется store.

Store — это как база данных, которая знает всю правду о приложении. Чтобы изменить что‑то в store, нужно создать и отправить action.

Action — это как сообщение, которое говорит, что нужно сделать с данными, например, добавить что‑то в список, поменять что‑то в форме, загрузить что‑то из интернета и т. д. Action не меняет данные сам, а только передает их в reducer.

Reducer — это как правило, которое говорит, как обновить store в зависимости от action. Reducer не портит старые данные, а создает новые на их основе. Все reducers собираются в один большой reducer, который обновляет store.

Чтобы компоненты React могли видеть и использовать данные из store, нужно подключить их с помощью библиотеки react‑redux. Она дает компонент Provider, который дает доступ к store всему приложению, и функцию connect, которая выбирает нужные данные из store и передает их в компоненты в виде пропсов. Также она дает возможность передавать в компоненты функции для создания и отправки actions. Когда store меняется, все подключенные компоненты обновляются с новыми данными.

Односторонний поток данных ReduxГифка из официальной документации
Односторонний поток данных Redux
Гифка из официальной документации

Вот пример кода, который демонстрирует использование Redux в приложении на React:

Код использования Redux
// actions.js
// определяем типы actions
export const ADD_TODO = 'ADD_TODO';
export const TOGGLE_TODO = 'TOGGLE_TODO';

// определяем action creators
export function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  };
}

export function toggleTodo(index) {
  return {
    type: TOGGLE_TODO,
    index
  };
}

// reducers.js
// определяем начальное состояние store
const initialState = {
  todos: []
};

// определяем reducer для обработки actions
function todoApp(state = initialState, action) {
  switch (action.type) {
    case ADD_TODO:
      // возвращаем новое состояние с добавленным элементом в массив todos
      return {
        ...state,
        todos: [
          ...state.todos,
          {
            text: action.text,
            completed: false
          }
        ]
      };
    case TOGGLE_TODO:
      // возвращаем новое состояние с переключенным флагом completed у элемента в массиве todos по индексу
      return {
        ...state,
        todos: state.todos.map((todo, index) => {
          if (index === action.index) {
            return {
              ...todo,
              completed: !todo.completed
            };
          }
          return todo;
        })
      };
    default:
      // возвращаем текущее состояние, если action не распознан
      return state;
  }
}

// index.js
// импортируем React, Redux и react-redux
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider, connect } from 'react-redux';

// импортируем наш reducer и action creators
import todoApp from './reducers';
import { addTodo, toggleTodo } from './actions';

// создаем store с помощью нашего reducer
const store = createStore(todoApp);

// определяем компонент для отображения одного элемента списка
const Todo = ({ text, completed, onClick }) => (
  <li
    style={{
      textDecoration: completed ? 'line-through' : 'none'
    }}
    onClick={onClick}
  >
    {text}
  </li>
);

// определяем компонент для отображения списка элементов
const TodoList = ({ todos, onTodoClick }) => (
  <ul>
    {todos.map((todo, index) => (
      <Todo
        key={index}
        {...todo}
        onClick={() => onTodoClick(index)}
      />
    ))}
  </ul>
);

// определяем компонент для ввода нового элемента
const AddTodo = ({ onAddClick }) => {
  let input;

  return (
    <div>
      <input ref={node => (input = node)} />
      <button
        onClick={() => {
          onAddClick(input.value);
          input.value = '';
        }}
      >
        Add Todo
      </button>
    </div>
  );
};

// определяем компонент для отображения всего приложения
const App = ({ todos, addTodo, toggleTodo }) => (
  <div>
    <AddTodo onAddClick={addTodo} />
    <TodoList todos={todos} onTodoClick={toggleTodo} />
  </div>
);

// определяем функцию, которая определяет, какие части store нужны компоненту App в виде пропсов
const mapStateToProps = state => ({
  todos: state.todos
});

// определяем функцию, которая определяет, какие action creators нужны компоненту App в виде пропсов
const mapDispatchToProps = dispatch => ({
  addTodo: text => dispatch(addTodo(text)),
  toggleTodo: index => dispatch(toggleTodo(index))
});

// подключаем компонент App к store с помощью функции connect
const ConnectedApp = connect(
  mapStateToProps,
  mapDispatchToProps
)(App);

// рендерим компонент Provider, который передает store в контекст, и компонент ConnectedApp внутри него
ReactDOM.render(
  <Provider store={store}>
    <ConnectedApp />
  </Provider>,
  document.getElementById('root')
);

Что хорошего в Redux?

  • Redux обеспечивает прозрачность и предсказуемость потока данных, так как все изменения состояния происходят только через actions и reducers, которые являются чистыми функциями, не зависящими от внешних факторов. Это упрощает отладку, тестирование и отслеживание изменений состояния, а также позволяет использовать различные инструменты и расширения, которые улучшают разработку и документирование кода.

    Например: Redux DevTools — это расширение для браузера, которое позволяет просматривать историю actions и состояния, а также перематывать их во времени, изменять состояние и actions, и многое другое.

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

Что плохого в Redux?

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

    Например: Для создания простого приложения на Redux нужно определить actions, action creators, reducers, store, Provider, connect, mapStateToProps, mapDispatchToProps и т. д.

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

    Например: для добавления нового action нужно определить его тип, action creator, обработчик в reducer, импортировать и экспортировать их, а также передать action creator в mapDispatchToProps и вызвать его из компонента.

  • Redux может приводить к проблемам производительности и избыточным перерисовкам, так как он обновляет store при каждом action, и перерисовывает все компоненты, которые подписаны на него, даже если их данные не изменились. Для решения этой проблемы нужно использовать дополнительные техники и библиотеки, такие как reselect, memo, useMemo, useCallback и т. д.

Mobx

Mobx — это стейт менеджер, основанный на концепции реактивного программирования (reactive programming).

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

Для связи компонентов React с observables используется библиотека mobx‑react, которая предоставляет декоратор @observer, который оборачивает компоненты в специальные функции, называемые reactions, которые автоматически подписываются на observables, которые используются в рендере компонента, и перерисовывают компонент, когда они изменяются.

Также mobx‑react предоставляет компонент Provider, который позволяет передавать observables в контекст, и функцию inject, которая позволяет получать observables из контекста в виде пропсов.

by Nethmee Kumararatne

Вот пример кода, который демонстрирует использование Mobx в приложении на React:

Код использования MobX
// store.js
// импортируем Mobx
import { observable, action } from 'mobx';

// определяем класс для хранения данных
class TodoStore {
  // определяем массив для хранения элементов списка
  @observable todos = [];

  // определяем действие для добавления нового элемента в список
  @action
  addTodo = text => {
    this.todos.push({
      text,
      completed: false
    });
  };

  // определяем действие для переключения флага completed у элемента в списке по индексу
  @action
  toggleTodo = index => {
    this.todos[index].completed = !this.todos[index].completed;
  };
}

// создаем экземпляр класса и экспортируем его
const store = new TodoStore();
export default store;

// index.js
// импортируем React и mobx-react
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider, observer, inject } from 'mobx-react';

// импортируем наш store
import store from './store';

// определяем компонент для отображения одного элемента списка
const Todo = ({ text, completed, onClick }) => (
  <li
    style={{
      textDecoration: completed ? 'line-through' : 'none'
    }}
    onClick={onClick}
  >
    {text}
  </li>
);

// определяем компонент для отображения списка элементов
const TodoList = observer(({ todos, onTodoClick }) => (
  <ul>
    {todos.map((todo, index) => (
      <Todo
        key={index}
        {...todo}
        onClick={() => onTodoClick(index)}
      />
    ))}
  </ul>
));

// определяем компонент для ввода нового элемента
const AddTodo = ({ onAddClick }) => {
  let input;

  return (
    <div>
      <input ref={node => (input = node)} />
      <button
        onClick={() => {
          onAddClick(input.value);
          input.value = '';
        }}
      >
        Add Todo
      </button>
    </div>
  );
};

// определяем компонент для отображения всего приложения
const App = ({ store }) => (
  <div>
    <AddTodo onAddClick={store.addTodo} />
    <TodoList todos={store.todos} onTodoClick={store.toggleTodo} />
  </div>
);

// оборачиваем компонент App в функцию inject, которая передает store в виде пропса
const ConnectedApp = inject('store')(App);

// рендерим компонент Provider, который передает store в контекст, и компонент ConnectedApp внутри него
ReactDOM.render(
  <Provider store={store}>
    <ConnectedApp />
  </Provider>,
  document.getElementById('root')
);

Что хорошего в Mobx?

  • Mobx обеспечивает простоту и удобство потока данных, так как он не требует создания и обработки actions и reducers, а позволяет изменять данные напрямую с помощью обычных операций, а также автоматически подписывает и перерисовывает компоненты, которые используют observables.
    Это снижает количество кода и бойлерплейта, а также упрощает понимание и отслеживание изменений состояния.

  • Mobx позволяет адаптировать приложения, так как он не навязывает строгую архитектуру и паттерны, а предоставляет гибкость и свободу в выборе структуры и организации данных.
    Это позволяет использовать Mobx в разных сценариях и совмещать его с другими библиотеками и решениями.

  • Mobx поддерживает оптимизацию приложений, так как он минимизирует количество перерисовок, выполняя их только тогда, когда observables, которые используются в компоненте, действительно изменились.
    Компонент перерисовывается только тогда, когда меняется то свойство объекта, которое он использует. Если свойство не используется, то изменение его не влияет на рендер
    Это повышает производительность и эффективность приложений, особенно при работе с большими и сложными данными.

Что плохого в Mobx?

  • Mobx может приводить к непонятности и неочевидности потока данных, так как он скрывает многие детали и механизмы работы observables, reactions и actions, а также не требует явного определения источников и потребителей данных.
    Это может затруднять отладку, тестирование и отслеживание изменений состояния, а также приводить к ошибкам и неожиданному поведению приложения.

  • Mobx может приводить к зависимости и несовместимости приложений, так как он использует многие специфичные и экспериментальные возможности JavaScript, такие как декораторы, прокси, генераторы и т. д., которые не поддерживаются всеми браузерами и средами.
    Это требует использования дополнительных инструментов и конфигураций, таких как Babel, Webpack, TypeScript и т. д., которые могут усложнять и замедлять процесс разработки и сборки приложений.

Redux Toolkit и mobx-state-tree

mobx‑state‑tree — это расширение Mobx, которое добавляет концепцию дерева состояния (state tree) с типизацией, валидацией, иммутабельностью и снапшотами.

Redux Toolkit — это официальный набор инструментов для работы с Redux, который упрощает настройку и использование Redux в React‑приложениях.

Mobx‑state‑tree и Redux Toolkit — это две библиотеки для управления состоянием в React‑приложениях, которые имеют разные подходы, преимущества и недостатки. Вкратце, их можно сравнить по следующим критериям:

ВАЖНО: В некоторый сравнениях аббревиатура mobx-state-tree подрузумевает использование стандартных возможностей MobX, то есть частично это сравнение в том числе чистого MobX с Redux Toolkit

Сложность и скорость разработки

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

  • Mobx‑state‑tree имеет преимущество в этом критерии, так как он предлагает более декларативный и интуитивный подход к управлению состоянием. Вы можете:

    • определить наблюдаемые данные и вычисляемые значения с помощью декораторов или функций;

    • использовать их в компонентах с помощью специальных оберток, таких как observer или inject;

    • описать структуру и правила дерева состояния с помощью типов и действий;

    • работать с иммутабельными снапшотами и патчами.

    Mobx‑state‑tree требует меньше кода и бойлерплейта, чем Redux Toolkit, и позволяет писать более выразительный и читаемый код.

  • Redux Toolkit, с другой стороны, имеет более строгий и формальный подход к управлению состоянием. Вы должны:

    • следовать концепциям и паттернам Redux, таким как однонаправленный поток данных, чистые функции, иммутабельность и нормализация;

    • использовать функции и хуки Redux Toolkit для создания и настройки хранилища, определения действий и редьюсеров, а также подключения компонентов к хранилищу.

    Redux Toolkit требует больше кода и бойлерплейта, чем Mobx (mobx‑state‑tree), и позволяет писать более строгий и надежный код.

Производительность и оптимизация

Как быстро и эффективно работает библиотека? Как она влияет на производительность и размер приложения? Какие есть способы оптимизации и улучшения производительности?

Mobx (mobx‑state‑tree) и Redux Toolkit имеют разные подходы к оптимизации и производительности, которые имеют свои преимущества и недостатки.

  • Mobx‑state‑tree использует реактивную модель, которая автоматически отслеживает и обновляет зависимости между данными и компонентами. Это позволяет избежать лишних рендеров и перерасчетов, а также делать минимальные изменения в DOM.
    Однако, Mobx (mobx‑state‑tree) также имеет накладные расходы на создание и поддержку наблюдателей (observers) и реакций (reactions), а также потенциальные проблемы с утечками памяти и циклическими зависимостями.
    Mobx (mobx‑state‑tree) также может быть сложнее оптимизировать для серверного рендеринга и код‑сплиттинга.

  • Redux Toolkit использует функциональную модель, которая основана на чистых функциях и иммутабельных данных. Это позволяет легко тестировать и отлаживать приложение, а также использовать различные инструменты и техники для оптимизации, такие как серверный рендеринг, код‑сплиттинг, персистентность, селекторы, мемоизация и т. д.
    Однако, Redux Toolkit также имеет накладные расходы на создание и поддержку хранилища, действий и редьюсеров, а также потенциальные проблемы с избыточностью и денормализацией данных. Redux Toolkit также может быть сложнее оптимизировать для минимальных рендеров и изменений в DOM.

Тестирование и отладка

Как легко и надежно можно тестировать и отлаживать приложение? Какие есть инструменты и методы для тестирования и отладки?

  • Mobx‑state‑tree позволяет писать более простые и декларативные тесты, так как он не требует мокать или имитировать действия и редьюсеры, а также предоставляет возможность сравнивать и восстанавливать снапшоты дерева состояния.
    Однако, Mobx‑state‑tree также может быть сложнее отлаживать, так как он скрывает многие детали и механизмы работы наблюдаемых данных и реакций, а также не имеет такого же уровня интеграции с инструментами разработчика, как Redux Toolkit.

  • Redux Toolkit позволяет писать более строгие и формальные тесты, так как он основан на чистых функциях и иммутабельных данных, а также предоставляет функции и хуки для тестирования действий и редьюсеров.
    Однако, Redux Toolkit также может быть сложнее тестировать, так как он требует больше кода и бойлерплейта для тестов, а также может иметь проблемы с асинхронными действиями и сайд‑эффектами. Redux Toolkit имеет преимущество в отладке, так как он позволяет легко просматривать и изменять историю и состояние хранилища с помощью инструментов разработчика, таких как Redux DevTools.

Экосистема и поддержка

Как много и качественно ресурсов, документации, сообщества и библиотек доступно для библиотеки? Как активно и стабильно развивается и поддерживается библиотека?

  • Mobx‑state‑tree и Redux Toolkit имеют сильную и активную экосистему и поддержку, которая постоянно растет и улучшается. Обе библиотеки имеют:

    • хорошую и подробную документацию, которая охватывает все аспекты их использования;

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

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

    Однако, можно сказать, что Redux Toolkit имеет некоторое преимущество в этом критерии, так как он является официальным и рекомендованным набором инструментов для работы с Redux, который является одним из самых популярных и известных стейт менеджеров для React. Redux Toolkit наследует и усиливает все преимущества и ресурсы Redux, а также решает многие его недостатки и проблемы. Redux Toolkit также имеет большую и более зрелую экосистему и поддержку, чем Mobx‑state‑tree, так как он существует дольше и используется шире.

Примеры использования:

Вот пример кода, который демонстрирует использование Redux Toolkit и mobx‑state‑tree в React приложении:

Redux Toolkit

Код
// Импортируем функции из Redux Toolkit
import { configureStore, createSlice, createAsyncThunk } from '@reduxjs/toolkit'
import { useSelector, useDispatch } from 'react-redux'

// Определяем асинхронное действие, которое получает авторов из API
const fetchAuthors = createAsyncThunk(
  'authors/fetchAuthors',
  async () => {
    const response = await fetch('https://jsonplaceholder.typicode.com/users')
    return response.json()
  }
)

// Определяем слайс, который управляет состоянием авторов
const authorsSlice = createSlice({
  name: 'authors',
  initialState: {
    list: [],
    status: 'idle',
    error: null
  },
  reducers: {
    // Определяем редьюсеры для синхронных действий
    addAuthor(state, action) {
      state.list.push(action.payload)
    },
    deleteAuthor(state, action) {
      state.list = state.list.filter(author => author.id !== action.payload)
    }
  },
  extraReducers: {
    // Определяем редьюсеры для асинхронных действий
    [fetchAuthors.pending]: (state, action) => {
      state.status = 'loading'
    },
    [fetchAuthors.fulfilled]: (state, action) => {
      state.status = 'succeeded'
      state.list = action.payload
    },
    [fetchAuthors.rejected]: (state, action) => {
      state.status = 'failed'
      state.error = action.error.message
    }
  }
})

// Экспортируем создатели действий и редьюсер
export const { addAuthor, deleteAuthor } = authorsSlice.actions
export default authorsSlice.reducer

// Создаем хранилище с редьюсером авторов
const store = configureStore({
  reducer: authorsSlice.reducer
})

// Готово к использованию в React-компоненте, если это ваша цель.
import React, { useEffect } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { fetchAuthors, addAuthor, deleteAuthor } from './authorsSlice'

const AuthorsList = () => {
  // Получаем состояние авторов из хранилища
  const authors = useSelector(state => state.authors)

  // Получаем функцию dispatch из хранилища
  const dispatch = useDispatch()

  // Получаем авторов при монтировании компонента
  useEffect(() => {
    dispatch(fetchAuthors())
  }, [dispatch])

  // Обрабатываем клик по кнопке добавить автора
  const handleAddAuthor = () => {
    const newAuthor = {
      id: Math.random(),
      name: 'New author',
      username: 'new_author',
      email: 'new@author.com'
    }
    dispatch(addAuthor(newAuthor))
  }

  // Обрабатываем клик по кнопке удалить автора
  const handleDeleteAuthor = (id) => {
    dispatch(deleteAuthor(id))
  }

  // Рендерим список авторов
  return (
    <div>
      <h1>Authors</h1>
      <button onClick={handleAddAuthor}>Add author</button>
      {authors.status === 'loading' && <p>Loading...</p>}
      {authors.status === 'failed' && <p>Error: {authors.error}</p>}
      {authors.status === 'succeeded' && (
        <ul>
          {authors.list.map(author => (
            <li key={author.id}>
              <p>{author.name}</p>
              <p>{author.username}</p>
              <p>{author.email}</p>
              <button onClick={() => handleDeleteAuthor(author.id)}>Delete author</button>
            </li>
          ))}
        </ul>
      )}
    </div>
  )
}

export default AuthorsList

Примечание к коду:

В этом коде используется Redux Toolkit для управления состоянием авторов в приложении на React. В этом коде есть две функции: handleAddAuthor и handleDeleteAuthor, которые вызываются при клике на соответствующие кнопки. Эти функции диспатчат действия addAuthor и deleteAuthor, которые определены в файле authorsSlice.js. Эти действия изменяют массив authors в хранилище, добавляя или удаляя авторов. Компонент AuthorsList рендерит список авторов из хранилища, используя хук useSelector. Компонент также использует хук useDispatch для получения функции dispatch, которая нужна для диспатча действий. Компонент также использует эффект, чтобы получить авторов из API при монтировании, используя асинхронное действие fetchAuthors, которое также определено в файле authorsSlice.js.

  • В этом коде используются следующие функции из Redux Toolkit:

    • configureStore: обертка для createStore, которая упрощает настройку хранилища с настройками по умолчанию. Позволяет автоматически комбинировать отдельные частичные редьюсеры, добавлять промежуточные слои или посредников, по умолчанию включает redux‑thunk, позволяет использовать инструменты разработчика Redux.

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

    • createAsyncThunk: принимает тип действия и функцию, возвращающую промис, и генерирует thunk, отправляющий типы действий pending/fulfilled/rejected на основе промиса.

    • useSelector: хук, который позволяет получить часть состояния из хранилища, используя селектор‑функцию

mobx-state-tree

Код
// Импортируем функции из mobx-state-tree
import { types } from "mobx-state-tree" // alternatively: import { t } from "mobx-state-tree"

// Определяем модель автора с идентификатором и именем
const Author = types.model({
  id: types.identifier, // уникальный идентификатор для каждого автора
  firstName: types.string, // имя автора
  lastName: types.string // фамилия автора
})

// Определяем модель поста с идентификатором, автором, текстом и временем
const Post = types.model({
  id: types.identifier, // уникальный идентификатор для каждого поста
  author: types.reference(Author), // ссылка на модель автора по его id
  text: types.string, // текст поста
  timestamp: types.number // время создания поста в миллисекундах
})

// Определяем модель хранилища, которая содержит массивы авторов и постов
const Store = types.model({
  authors: types.array(Author), // массив моделей авторов
  posts: types.array(Post) // массив моделей постов
}).actions(self => ({ // определяем действия для изменения данных в хранилище
  // Добавляем пост в массив posts
  handleAddPost() {
    const newPost = {
      id: Math.random(), // генерируем случайный id
      author: self.authors[0].id, // берем id первого автора из массива authors
      text: "This is a new post", // задаем текст поста
      timestamp: Date.now() // берем текущее время
    }
    self.posts.push(newPost) // добавляем новый пост в конец массива posts
  },
  // Удаляем пост из массива posts по id
  handleDeletePost(id) {
    self.posts = self.posts.filter(post => post.id !== id) // фильтруем массив posts, оставляя только те посты, у которых id не равен переданному id
  }
}))

// Создаем несколько экземпляров моделей авторов
const alice = Author.create({ id: "alice", firstName: "Alice", lastName: "Smith" })
const bob = Author.create({ id: "bob", firstName: "Bob", lastName: "Jones" })

// Создаем несколько экземпляров моделей постов
const post1 = Post.create({ id: "1", author: alice.id, // передаем только id автора
  text: "Hello world!",
  timestamp: Date.now()
})
const post2 = Post.create({ id: "2", author: bob.id, // передаем только id автора
  text: "This is a post",
  timestamp: Date.now() + 1000
})

// Создаем экземпляр модели хранилища с массивами авторов и постов
const store = Store.create({ authors: [alice, bob], posts: [post1, post2] })

// Готово к использованию в React-компоненте, если это ваша цель.
import { observer } from "mobx-react-lite" // импортируем функцию observer из mobx-react-lite
const PostsList = observer((props) => { // оборачиваем компонент в observer, чтобы он реагировал на изменения данных
  return (
    <div>
      <h1>Posts</h1>
      <button onClick={store.handleAddPost}>Add post</button> // добавляем кнопку для добавления поста, которая вызывает действие handleAddPost из хранилища
      <ul>
        {store.posts.map(post => ( // перебираем массив постов из хранилища
          <li key={post.id}>
            <p>{post.text}</p> // выводим текст поста
            <p>By {post.author.firstName} {post.author.lastName}</p> // выводим имя и фамилию автора поста по ссылке
            <p>{new Date(post.timestamp).toLocaleString()}</p> // выводим время поста в читаемом формате
            <button onClick={() => store.handleDeletePost(post.id)}>Delete post</button> // добавляем кнопку для удаления поста, которая вызывает действие handleDeletePost из хранилища и передает id поста
          </li>
        ))}
      </ul>
    </div>
  )
})

Примечание к коду:

Mobx‑state‑tree позволяет изменять данные в дереве состояния с помощью действий (actions), которые являются специальными функциями, определенными в моделях. Действия могут быть синхронными или асинхронными, и они автоматически применяются к снапшотам (snapshots) и патчам (patches) дерева состояния.

В этом коде есть два примера действий: handleAddPost и handleDeletePost, которые добавляют и удаляют посты из массива posts в хранилище. Эти действия изменяют данные в дереве состояния, используя методы push и filter массива. Когда эти действия вызываются, mobx‑state‑tree автоматически создает снапшот и патч, отражающие изменения в дереве состояния, и обновляет компоненты, которые зависят от этих данных.

Примечание: mobx‑state‑tree использует библиотеку Immer для работы с иммутабельными данными. Это означает, что вы можете писать код, как будто вы изменяете мутабельные данные, но на самом деле вы не меняете исходный объект, а создаете новый. Это упрощает код и избегает ошибок, связанных с мутабельностью.

Вывод

Redux и Mobx — это два разных подхода к управлению состоянием и данными в приложениях на React, которые имеют свои преимущества и недостатки.

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

Кому он подходит?

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

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

Кому он подходит?

Mobx (mobx-state-tree) подходит для проектов, которые хотят иметь более декларативный, интуитивный и выразительный код, а также более автоматическую и гибкую синхронизацию состояния и представления.
Также лучше подходит для проектов, которые не требуют высокой производительности, сложной логики или большого масштаба и для проектов, которые хотят экспериментировать и инновировать, в частности, кто предпочитает объектно-ориентированный стиль программирования.

Выбор между Redux и Mobx зависит от многих факторов, таких как размер, сложность, цель и специфика приложения, а также предпочтения, опыт и навыки разработчиков. Нет однозначного ответа на вопрос, какой из них лучше подходит для разработки современных приложений на React в 2024 году.

Возможно, что в некоторых случаях лучше использовать их вместе (следите за блогом, скоро напишу про это статью), а в некоторых — вообще обойтись без них, используя встроенные средства React, такие как useState, useReducer, useContext и т. д. Главное — понимать принципы и особенности каждого из них, и выбирать тот, который наиболее соответствует потребностям и задачам конкретного проекта.

Аббревиатуры

Снапшоты — это иммутабельные копии дерева состояния в определенный момент времени.
Патчи — это небольшие изменения, которые произошли в дереве состояния.
Персистентность — это свойство данных или объектов сохраняться после завершения работы программы, которая их создала или использовала.
Код‑сплиттинг — это техника оптимизации производительности веб‑приложений, которая заключается в разделении кода на несколько частей (чанков), которые загружаются по мере необходимости.
Денормализация — это процесс преобразования нормализованной структуры данных в менее нормализованную, с целью увеличения скорости обработки запросов.
Иммутабельный — это неизменяемый, то есть не меняющийся после создания.
Бойлерплейт — это шаблонный или повторяющийся код, который нужен для стандартной или часто используемой функциональности.

Статьи

Если вы все же не определились между Redux и MobX, я предложу вам эти статьи:

  • MobX vs Redux — What to Choose in 2024? — Aglowid IT Solutions — эта статья дает подробное сравнение между MobX и Redux, а также дает таблицу с основными аспектами их работы.

  • Should You Still Go with Redux in 2024? Exploring Alternatives and Comparisons — Medium — эта статья рассматривает не только MobX и Redux, но и другие альтернативы, такие как React Context API, Recoil и Zustand, а также дает рекомендации по их выбору.

  • reactjs — Difference between: MobX and Redux — Stack Overflow — это ответ на вопрос на популярном сайте для разработчиков, который дает краткий обзор преимуществ и недостатков MobX и Redux.

  • Why Infinite Red uses MobX‑State‑Tree instead of Redux — статья, в которой автор объясняет, почему его компания предпочитает использовать Mobx‑state‑tree вместо Redux для разработки приложений на React, а также дает обзор основных концепций и преимуществ Mobx‑state‑tree.

  • Redux‑toolkit и переиспользование кода — статья, в которой автор демонстрирует, как можно переиспользовать код и логику при работе с Redux Toolkit, используя функции createSlice и createAsyncThunk, а также дает сравнение с классическим подходом Redux.

Заключение

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

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

Tags:
Hubs:
Total votes 14: ↑12 and ↓2+12
Comments79

Articles