State Management в React: от Context до Zustand

Выбор правильного решения для управления состоянием

Управление состоянием — одна из самых сложных задач в React приложениях. Неправильный выбор приводит к prop drilling, избыточным ререндерам и сложности в maintenance. Разберем все варианты от простых до продвинутых. Встроенные решения React 1. useState: локальное состояние function Counter() { const [count, setCount] = useState(0); return ( setCount(count + 1)}> Count: {count} ); } Когда использовать: состояние внутри одного компонента 2. useReducer: сложная логика const initialState = { count: 0, history: [] }; function reducer(state, action) { switch (action.type) { case 'increment': return { count: state.count + 1, history: [...state.history, state.count] }; case 'reset': return initialState; default: return state; } } function Counter() { const [state, dispatch] = useReducer(reducer, initialState); return ( <> Count: {state.count} dispatch({ type: 'increment' })}> Increment ); } Когда использовать: сложная логика обновления, множественные sub-values 3. Context API: глобальное состояние const ThemeContext = createContext(); function ThemeProvider({ children }) { const [theme, setTheme] = useState('dark'); return ( {children} ); } function useTheme() { const context = useContext(ThemeContext); if (!context) { throw new Error('useTheme must be used within ThemeProvider'); } return context; } // Использование function Button() { const { theme, setTheme } = useTheme(); return {theme} ; } Проблемы Context: Все consumers ререндерятся при любом изменении Нет оптимизации селекторов Verbose для множественных контекстов Redux: классическое решение Redux Toolkit (современный подход): import { createSlice, configureStore } from '@reduxjs/toolkit'; // Slice const counterSlice = createSlice({ name: 'counter', initialState: { value: 0 }, reducers: { increment: (state) => { state.value += 1; // Immer под капотом }, decrement: (state) => { state.value -= 1; }, incrementByAmount: (state, action) => { state.value += action.payload; } } }); export const { increment, decrement, incrementByAmount } = counterSlice.actions; // Store const store = configureStore({ reducer: { counter: counterSlice.reducer } }); // Component function Counter() { const count = useSelector((state) => state.counter.value); const dispatch = useDispatch(); return ( dispatch(increment())}> Count: {count} ); } Async с Redux Toolkit: import { createAsyncThunk } from '@reduxjs/toolkit'; const fetchUser = createAsyncThunk( 'users/fetchById', async (userId) => { const response = await fetch(`/api/users/${userId}`); return response.json(); } ); const userSlice = createSlice({ name: 'user', initialState: { data: null, loading: false, error: null }, reducers: {}, extraReducers: (builder) => { builder .addCase(fetchUser.pending, (state) => { state.loading = true; }) .addCase(fetchUser.fulfilled, (state, action) => { state.loading = false; state.data = action.payload; }) .addCase(fetchUser.rejected, (state, action) => { state.loading = false; state.error = action.error.message; }); } }); Плюсы Redux: Предсказуемое состояние Time-travel debugging Middleware (logging, async) Огромная экосистема Минусы: Boilerplate (хотя RTK уменьшил) Кривая обучения Overhead для простых приложений Zustand: минимализм и простота import create from 'zustand'; const useStore = create((set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })), decrement: () => set((state) => ({ count: state.count - 1 })), reset: () => set({ count: 0 }) })); // Использование function Counter() { const count = useStore((state) => state.count); const increment = useStore((state) => state.increment); return Count: {count} ; } Async в Zustand: const useStore = create((set) => ({ users: [], loading: false, fetchUsers: async () => { set({ loading: true }); const users = await fetch('/api/users').then(r => r.json()); set({ users, loading: false }); } })); Middleware: import { persist } from 'zustand/middleware'; const useStore = create( persist( (set) => ({ theme: 'dark', setTheme: (theme) => set({ theme }) }), { name: 'theme-storage', // localStorage key } ) ); Плюсы Zustand: Минимальный boilerplate Отличная производительность Маленький размер (1KB) Простой API Jotai: атомарный подход import { atom, useAtom } from 'jotai'; // Атомы const countAtom = atom(0); const doubleCountAtom = atom((get) => get(countAtom) * 2); function Counter() { const [count, setCount] = useAtom(countAtom); const [doubleCount] = useAtom(doubleCountAtom); return ( <> Count: {count} Double: {doubleCount} setCount(c => c + 1)}>Increment ); } Async атомы: const userAtom = atom(async (get) => { const userId = get(userIdAtom); const response = await fetch(`/api/users/${userId}`); return response.json(); }); Плюсы Jotai: Минимальный boilerplate Отличная производительность (granular updates) TypeScript-friendly Композиция атомов Recoil: от Facebook import { atom, selector, useRecoilState, useRecoilValue } from 'recoil'; const countState = atom({ key: 'countState', default: 0, }); const doubleCountState = selector({ key: 'doubleCountState', get: ({ get }) => { const count = get(countState); return count * 2; }, }); function Counter() { const [count, setCount] = useRecoilState(countState); const doubleCount = useRecoilValue(doubleCountState); return {count} / {doubleCount} ; } Особенности: async selectors, time-travel, React Suspense integration MobX: реактивность через Observable import { makeObservable, observable, action } from 'mobx'; import { observer } from 'mobx-react-lite'; class CounterStore { count = 0; constructor() { makeObservable(this, { count: observable, increment: action, }); } increment() { this.count++; } } const counterStore = new CounterStore(); const Counter = observer(() => { return ( counterStore.increment()}> Count: {counterStore.count} ); }); Плюсы MobX: минимальный boilerplate, автоматическая оптимизация, OOP подход Сравнительная таблица Решение Размер Сложность Производительность Context 0 KB (встроен) Низкая Средняя Redux Toolkit 11 KB Средняя Высокая Zustand 1 KB Низкая Высокая Jotai 3 KB Средняя Высокая Recoil 20 KB Средняя Высокая MobX 16 KB Средняя Высокая Когда что использовать Context API: Простые глобальные значения (theme, locale) Редко изменяющееся состояние Нет требований к производительности Redux: Большие приложения с сложной логикой Нужен time-travel debugging Команда знакома с Redux Middleware критичен Zustand: Средние/большие приложения Нужна простота + производительность Minimal boilerplate Jotai/Recoil: Атомарные обновления Сложные derived states React Suspense integration MobX: Предпочтение OOP Автоматическая оптимизация Миграция с Angular/Vue Performance оптимизация 1. Селекторы: // Плохо - ререндер при любом изменении const state = useStore(); // Хорошо - ререндер только при изменении count const count = useStore((state) => state.count); 2. Мемоизация селекторов: import { createSelector } from 'reselect'; const selectUsers = (state) => state.users; const selectFilter = (state) => state.filter; const selectFilteredUsers = createSelector( [selectUsers, selectFilter], (users, filter) => users.filter(u => u.name.includes(filter)) ); 3. Code splitting: // Ленивая загрузка части store const useUserStore = create(() => ({ loadUserModule: async () => { const module = await import('./userStore'); return module.userState; } })); Заключение: Нет универсального решения для state management. Начните с встроенных инструментов React. Если Context не справляется — выберите внешнюю библиотеку исходя из размера приложения, требований к performance и опыта команды. Для новых проектов рекомендую Zustand — идеальный баланс простоты и возможностей.