Redux vs Mobx кого же выбрать для React-приложения в 2024 году?
Привет, Хабр!
Сегодня я хочу поделиться с вами своими размышлениями о том, какой стейт менеджер лучше использовать для разработки приложений на React в 2024 году. Как вы знаете, React — это одна из самых популярных и мощных библиотек для создания пользовательских интерфейсов, которая предоставляет множество возможностей и преимуществ для разработчиков. Однако, по мере роста и усложнения приложений на React, возникает необходимость в управлении состоянием и данными, которые используются в разных компонентах. Для этого существуют различные решения, называемые стейт менеджерами. Стейт менеджер — это инструмент, который позволяет централизованно хранить, обновлять и передавать данные между компонентами, а также реагировать на изменения состояния.
В этой статье я рассмотрю два из самых популярных и зрелых стейт менеджеров для React: Redux и Mobx. Я сравню их основные принципы, преимущества и недостатки, а также покажу примеры их использования в коде. Также я попытаюсь ответить на вопрос, какой из них лучше подходит для разработки современных приложений на 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 в приложении на React:
// 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 }) => (
{text}
);
// определяем компонент для отображения списка элементов
const TodoList = ({ todos, onTodoClick }) => (
{todos.map((todo, index) => (
onTodoClick(index)}
/>
))}
);
// определяем компонент для ввода нового элемента
const AddTodo = ({ onAddClick }) => {
let input;
return (
(input = node)} />
);
};
// определяем компонент для отображения всего приложения
const App = ({ todos, addTodo, toggleTodo }) => (
);
// определяем функцию, которая определяет, какие части 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(
,
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 из контекста в виде пропсов.
Вот пример кода, который демонстрирует использование Mobx в приложении на React:
// 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 }) => (
{text}
);
// определяем компонент для отображения списка элементов
const TodoList = observer(({ todos, onTodoClick }) => (
{todos.map((todo, index) => (
onTodoClick(index)}
/>
))}
));
// определяем компонент для ввода нового элемента
const AddTodo = ({ onAddClick }) => {
let input;
return (
(input = node)} />
);
};
// определяем компонент для отображения всего приложения
const App = ({ store }) => (
);
// оборачиваем компонент App в функцию inject, которая передает store в виде пропса
const ConnectedApp = inject('store')(App);
// рендерим компонент Provider, который передает store в контекст, и компонент ConnectedApp внутри него
ReactDOM.render(
,
document.getElementById('root')
);
Что хорошего в Mobx?
Mobx обеспечивает простоту и удобство потока данных, так как он не требует создания и обработки actions и reducers, а позволяет изменять данные напрямую с помощью обычных операций, а также автоматически подписывает и перерисовывает компоненты, которые используют observables.
Это снижает количество кода и бойлерплейта, а также упрощает понимание и отслеживание изменений состояния.Mobx позволяет адаптировать приложения, так как он не навязывает строгую архитектуру и паттерны, а предоставляет гибкость и свободу в выборе структуры и организации данных.
Это позволяет использовать Mobx в разных сценариях и совмещать его с другими библиотеками и решениями.Mobx поддерживает оптимизацию приложений, так как он минимизирует количество перерисовок, выполняя их только тогда, когда observables, которые используются в компоненте, действительно изменились.
Это повышает производительность и эффективность приложений, особенно при работе с большими и сложными данными.
Что плохого в Mobx?
Mobx может приводить к непонятности и неочевидности потока данных, так как он скрывает многие детали и механизмы работы observables, reactions и actions, а также не требует явного определения источников и потребителей данных.
Это может затруднять отладку, тестирование и отслеживание изменений состояния, а также приводить к ошибкам и неожиданному поведению приложения.Mobx может приводить к зависимости и несовместимости приложений, так как он использует многие специфичные и экспериментальные возможности JavaScript, такие как декораторы, прокси, генераторы и т. д., которые не поддерживаются всеми браузерами и средами.
Это требует использования дополнительных инструментов и конфигураций, таких как Babel, Webpack, TypeScript и т. д., которые могут усложнять и замедлять процесс разработки и сборки приложений.
Вывод
Redux и Mobx — это два разных подхода к управлению состоянием и данными в приложениях на React, которые имеют свои преимущества и недостатки.
Redux предлагает прозрачный и предсказуемый поток данных, который облегчает отладку, тестирование и масштабирование приложений, но требует большого количества кода и бойлерплейта, а также может приводить к проблемам производительности и избыточным перерисовкам.
Подходит для больших и сложных приложений, которые нуждаются в структурированной и стабильной архитектуре, а также для тех, кто предпочитает функциональный стиль программирования.
Mobx предлагает простой и удобный поток данных, который снижает количество кода и бойлерплейта, а также оптимизирует перерисовки, но может приводить к непонятности и неочевидности изменений состояния, а также к зависимости и несовместимости приложений.
Подходит для маленьких и средних приложений, которые нуждаются в гибкости и адаптивности, а также для тех, кто предпочитает объектно-ориентированный стиль программирования.
Выбор между Redux и Mobx зависит от многих факторов, таких как размер, сложность, цель и специфика приложения, а также предпочтения, опыт и навыки разработчиков. Нет однозначного ответа на вопрос, какой из них лучше подходит для разработки современных приложений на React в 2024 году.
Возможно, что в некоторых случаях лучше использовать их вместе (следите за блогом, скоро напишу про это статью), а в некоторых — вообще обойтись без них, используя встроенные средства React, такие как useState, useReducer, useContext и т. д. Главное — понимать принципы и особенности каждого из них, и выбирать тот, который наиболее соответствует потребностям и задачам конкретного проекта.
Статьи
Если вы все же не определились между Redux и MobX, я предложу вам эти статьи:
Надеюсь, эта статья была полезна и интересна для вас, и помогла вам разобраться в различиях и сходствах между Redux и Mobx. Если у вас есть вопросы, комментарии или пожелания, пожалуйста, напишите мне в комментариях или в личных сообщениях.
Спасибо за внимание!