Redux vs Mobx кого же выбрать для React-приложения в 2024 году?
- воскресенье, 25 февраля 2024 г. в 00:00:09
Привет, Хабр!
Сегодня я хочу поделиться с вами своими размышлениями о том, какой стейт менеджер лучше использовать для разработки приложений на React в 2024 году. Как вы знаете, React — это одна из самых популярных и мощных библиотек для создания пользовательских интерфейсов, которая предоставляет множество возможностей и преимуществ для разработчиков. Однако, по мере роста и усложнения приложений на React, возникает необходимость в управлении состоянием и данными, которые используются в разных компонентах. Для этого существуют различные решения, называемые стейт менеджерами. Стейт менеджер — это инструмент, который позволяет централизованно хранить, обновлять и передавать данные между компонентами, а также реагировать на изменения состояния.
В этой статье я рассмотрю два из самых популярных и зрелых стейт менеджеров для React: Redux и Mobx. Я сравню их основные принципы, преимущества и недостатки, а также покажу примеры их использования в коде. Также я попытаюсь ответить на вопрос, какой из них лучше подходит для разработки современных приложений на React в 2024 году.
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 в приложении на 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 }) => (
<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 обеспечивает прозрачность и предсказуемость потока данных, так как все изменения состояния происходят только через actions и reducers, которые являются чистыми функциями, не зависящими от внешних факторов. Это упрощает отладку, тестирование и отслеживание изменений состояния, а также позволяет использовать различные инструменты и расширения, которые улучшают разработку и документирование кода.
Например: Redux DevTools — это расширение для браузера, которое позволяет просматривать историю actions и состояния, а также перематывать их во времени, изменять состояние и actions, и многое другое.
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 — это стейт менеджер, основанный на концепции реактивного программирования (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 }) => (
<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 обеспечивает простоту и удобство потока данных, так как он не требует создания и обработки actions и reducers, а позволяет изменять данные напрямую с помощью обычных операций, а также автоматически подписывает и перерисовывает компоненты, которые используют observables.
Это снижает количество кода и бойлерплейта, а также упрощает понимание и отслеживание изменений состояния.
Mobx позволяет адаптировать приложения, так как он не навязывает строгую архитектуру и паттерны, а предоставляет гибкость и свободу в выборе структуры и организации данных.
Это позволяет использовать Mobx в разных сценариях и совмещать его с другими библиотеками и решениями.
Mobx поддерживает оптимизацию приложений, так как он минимизирует количество перерисовок, выполняя их только тогда, когда observables, которые используются в компоненте, действительно изменились.
Это повышает производительность и эффективность приложений, особенно при работе с большими и сложными данными.
Mobx может приводить к непонятности и неочевидности потока данных, так как он скрывает многие детали и механизмы работы observables, reactions и actions, а также не требует явного определения источников и потребителей данных.
Это может затруднять отладку, тестирование и отслеживание изменений состояния, а также приводить к ошибкам и неожиданному поведению приложения.
Mobx может приводить к зависимости и несовместимости приложений, так как он использует многие специфичные и экспериментальные возможности JavaScript, такие как декораторы, прокси, генераторы и т. д., которые не поддерживаются всеми браузерами и средами.
Это требует использования дополнительных инструментов и конфигураций, таких как Babel, Webpack, TypeScript и т. д., которые могут усложнять и замедлять процесс разработки и сборки приложений.
Redux и Mobx — это два разных подхода к управлению состоянием и данными в приложениях на React, которые имеют свои преимущества и недостатки.
Redux предлагает прозрачный и предсказуемый поток данных, который облегчает отладку, тестирование и масштабирование приложений, но требует большого количества кода и бойлерплейта, а также может приводить к проблемам производительности и избыточным перерисовкам.
Подходит для больших и сложных приложений, которые нуждаются в структурированной и стабильной архитектуре, а также для тех, кто предпочитает функциональный стиль программирования.
Mobx предлагает простой и удобный поток данных, который снижает количество кода и бойлерплейта, а также оптимизирует перерисовки, но может приводить к непонятности и неочевидности изменений состояния, а также к зависимости и несовместимости приложений.
Подходит для маленьких и средних приложений, которые нуждаются в гибкости и адаптивности, а также для тех, кто предпочитает объектно-ориентированный стиль программирования.
Выбор между 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.
Надеюсь, эта статья была полезна и интересна для вас, и помогла вам разобраться в различиях и сходствах между Redux и Mobx. Если у вас есть вопросы, комментарии или пожелания, пожалуйста, напишите мне в комментариях или в личных сообщениях.
Спасибо за внимание!