Привет, Хабр!
Сегодня я хочу поделиться с вами своими размышлениями о том, какой стейт менеджер лучше использовать для разработки приложений на 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 в приложении на 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 из контекста в виде пропсов.
Вот пример кода, который демонстрирует использование 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. Если у вас есть вопросы, комментарии или пожелания, пожалуйста, напишите мне в комментариях или в личных сообщениях.
Спасибо за внимание!