Когда нужен глобальный стор
Локальный стейт и контекст покрывают большинство случаев. Но иногда нужен глобальный стор — централизованное хранилище состояния.
Проблемы контекста
- Каждое изменение значения перерисовывает всех потребителей
- Сложно организовать несколько независимых кусков состояния
- Нет инструментов для отладки: devtools, time-travel
- Нет middleware для логирования, асинхронности
Что даёт стор
- Селекторы — компонент подписывается только на нужную часть состояния
- DevTools — просмотр истории изменений
- Middleware — логирование, асинхронные действия
- Модульность — несколько независимых слайсов
- Производительность — точечные обновления
Пример: Zustand
import { create } from 'zustand';
type CartStore = {
items: CartItem[];
add: (item: CartItem) => void;
remove: (id: number) => void;
total: () => number;
};
export const useCartStore = create<CartStore>((set, get) => ({
items: [],
add: (item) => set((s) => ({ items: [...s.items, item] })),
remove: (id) => set((s) => ({ items: s.items.filter((i) => i.id !== id) })),
total: () => get().items.reduce((sum, i) => sum + i.price, 0),
}));
Использование:
function CartButton({ item }: { item: CartItem }) {
const add = useCartStore((s) => s.add);
return <button onClick={() => add(item)}>В корзину</button>;
}
function CartTotal() {
const total = useCartStore((s) => s.total());
return <p>Итого: {total}</p>;
}
useCartStore((s) => s.add) — селектор. Компонент перерисовывается только при изменении выбранной части.
Redux Toolkit
Классический Redux. Больше boilerplate, но мощнее.
import { createSlice, configureStore } from '@reduxjs/toolkit';
const cartSlice = createSlice({
name: 'cart',
initialState: { items: [] as CartItem[] },
reducers: {
add: (state, action) => {
state.items.push(action.payload);
},
remove: (state, action) => {
state.items = state.items.filter((i) => i.id !== action.payload);
},
},
});
export const { add, remove } = cartSlice.actions;
export const store = configureStore({
reducer: { cart: cartSlice.reducer },
});
В компоненте:
import { useSelector, useDispatch } from 'react-redux';
function Cart() {
const items = useSelector((s: RootState) => s.cart.items);
const dispatch = useDispatch();
return (
<ul>
{items.map((item) => (
<li key={item.id}>
{item.name}
<button onClick={() => dispatch(remove(item.id))}>Удалить</button>
</li>
))}
</ul>
);
}
Другие сторы
- Jotai — атомарный подход, минимальный boilerplate
- Valtio — прокси-объекты, мутируемый синтаксис
- MobX — реактивный, классы и декораторы
Когда нужен стор
- Данные нужны во многих местах приложения
- Данные меняются часто
- Нужна история изменений и devtools
- Нужны middleware (логирование, API)
- Состояние большое и структурированное
Когда не нужен
- Данные локальные для одного компонента
- Данные нужны небольшой группе компонентов — контекст
- Серверные данные — TanStack Query / SWR
Серверные данные — отдельно
Серверные данные (списки, детали, пагинация) лучше держать в TanStack Query или SWR. Они кешируют, инвалидируют, повторяют запросы.
Стор — для клиентского состояния: тема, корзина, модалки, фильтры UI.
Итоги
- Стор — для глобального состояния
- Селекторы дают точечные обновления
- Zustand — минималистичный, Redux — мощный
- Не всё состояние нужно в сторе
- Серверные данные — отдельный слой