← Назад к вопросам

Зачем в State Manager класть карточки товара?

2.0 Middle🔥 251 комментариев
#State Management

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Зачем хранить карточки товара в State Manager

Хранение карточек товара в State Manager (Redux, Zustand, MobX и т.д.) - это принципиальное архитектурное решение, которое имеет множество серьёзных причин. Это не просто удобство, а лучшая практика для масштабируемых приложений.

Основные причины

1. Single Source of Truth (Единый источник истины)

Если карточки хранить только локально в компонентах, мы получим дублирование данных:

// ПЛОХО - данные раскиданы по компонентам
function ProductCard({ productId }) {
  const [product, setProduct] = useState(null);
  
  useEffect(() => {
    fetch(`/api/products/${productId}`)
      .then(r => r.json())
      .then(setProduct);
  }, [productId]);
  
  return <div>{product?.name}</div>;
}

// Если этот компонент отобразится 10 раз - 10 запросов на сервер!
// Или 10 разных состояний одного товара

// ХОРОШО - централизованное хранилище
function useProduct(productId) {
  return useSelector(state => 
    state.products.items[productId]
  );
}

// Любой компонент использует одни и те же данные

2. Дедупликация данных

Один товар может быть отображён в разных местах приложения одновременно:

// Сценарий:
// 1. Пользователь открыл карточку товара (ProductPage)
// 2. В углу экрана товар отображается в корзине (CartSidebar)
// 3. Товар рекомендуется в списке похожих (RelatedProducts)
// 4. Товар показан в истории просмотров (BrowsingHistory)

// БЕЗ State Manager - товар хранится в 4 разных местах
// Если цена изменилась - нужно обновить 4 копии

// С State Manager - один источник истины
const store = {
  products: {
    items: {
      '123': { id: '123', name: 'Товар', price: 100, ... }
    }
  }
};

// Все компоненты получают одну копию
function ProductCard({ productId }) {
  const product = useSelector(state => state.products.items[productId]);
  return <div>{product.name} - {product.price}</div>;
}

// Если обновить product['123'], все компоненты обновятся

3. Синхронизация между компонентами

Когда товар изменяется в одном месте, остальные компоненты должны знать об этом:

// Сценарий: пользователь добавил товар в корзину
// Нужно обновить:
// - Иконку корзины (повысить счётчик)
// - Баннер "Товар в корзине"
// - Кнопку "Добавить в корзину" -> "В корзине"
// - Страницу корзины (если открыта)

// С State Manager
const addToCart = (productId) => {
  dispatch({
    type: 'ADD_TO_CART',
    payload: { productId }
  });
  // Все подписанные компоненты обновятся автоматически
};

// Без State Manager - нужно пробрасывать callbacks через props
// или использовать EventEmitter - намного сложнее

4. Кэширование и оптимизация запросов

State Manager позволяет кэшировать данные и избежать дублирующихся запросов:

// С State Manager + Redux Thunk/Saga
const fetchProduct = (productId) => async (dispatch, getState) => {
  // Проверяем уже ли загружен товар
  const product = getState().products.items[productId];
  
  if (product) {
    // Уже есть в кэше, не делаем запрос
    return;
  }
  
  dispatch({ type: 'FETCH_PRODUCT_START' });
  
  try {
    const response = await fetch(`/api/products/${productId}`);
    const data = await response.json();
    
    dispatch({
      type: 'FETCH_PRODUCT_SUCCESS',
      payload: data
    });
  } catch (error) {
    dispatch({ type: 'FETCH_PRODUCT_ERROR', payload: error });
  }
};

// Без State Manager - сложно отслеживать кэш

5. Нормализация данных

Это критично для сложных связей между сущностями:

// БЕЗ нормализации (плохо - много дублирования)
const denormalizedData = [
  {
    id: 1,
    name: 'Товар 1',
    category: { id: 'cat1', name: 'Категория 1' },
    reviews: [
      { id: 'rev1', author: { id: 'user1', name: 'John' }, ... },
      { id: 'rev2', author: { id: 'user2', name: 'Jane' }, ... }
    ]
  }
];
// Если обновится категория или автор - нужно искать по всем товарам

// С нормализацией (хорошо - одна копия каждой сущности)
const normalizedData = {
  products: {
    '1': { id: 1, name: 'Товар 1', categoryId: 'cat1' }
  },
  categories: {
    'cat1': { id: 'cat1', name: 'Категория 1' }
  },
  reviews: {
    'rev1': { id: 'rev1', productId: '1', authorId: 'user1' },
    'rev2': { id: 'rev2', productId: '1', authorId: 'user2' }
  },
  users: {
    'user1': { id: 'user1', name: 'John' },
    'user2': { id: 'user2', name: 'Jane' }
  }
};
// Обновиться категория или автор - одно место

6. Отслеживание состояния загрузки

State Manager позволяет управлять состояниями:

const state = {
  products: {
    items: { /* товары */ },
    loading: false,
    error: null,
    loaded: false
  }
};

// С таким состоянием легко показать лоадер или ошибку
function ProductPage({ productId }) {
  const product = useSelector(state => state.products.items[productId]);
  const loading = useSelector(state => state.products.loading);
  const error = useSelector(state => state.products.error);
  
  if (loading) return <Spinner />;
  if (error) return <Error message={error} />;
  return <ProductCard product={product} />;
}

7. Time-Travel Debugging

С Redux DevTools можно просмотреть всю историю изменений состояния:

// Redux DevTools позволяет:
// - Просмотреть каждое изменение состояния
// - Откатиться на шаг назад
// - Посмотреть какой экшн привёл к изменению
// - Воспроизвести ошибку

// Это невозможно без State Manager

8. Middleware и побочные эффекты

State Manager позволяет обработать побочные эффекты однообразно:

// Пример Redux Thunk
const fetchProductWithRelated = (productId) => async (dispatch) => {
  // Загружаем товар
  dispatch(fetchProduct(productId));
  
  // Загружаем похожие товары
  dispatch(fetchRelatedProducts(productId));
  
  // Загружаем отзывы
  dispatch(fetchReviews(productId));
  
  // Логируем
  console.log(`Loaded product ${productId}`);
};

// Или с Saga/RTK Query - более сложные сценарии

9. Undo/Redo функциональность

Легко реализуется с State Manager:

const undoReducer = (state, action) => {
  switch(action.type) {
    case 'UNDO':
      return state.history[state.history.length - 2];
    case 'REDO':
      return state.history[state.history.length];
    default:
      return {
        ...state,
        history: [...state.history, state]
      };
  }
};

10. Тестирование

Чистое хранилище легче тестировать:

// Редюсер - чистая функция, легко тестировать
describe('productReducer', () => {
  it('should add product to cart', () => {
    const initialState = { items: [] };
    const action = {
      type: 'ADD_TO_CART',
      payload: { productId: '123' }
    };
    
    const newState = productReducer(initialState, action);
    
    expect(newState.items).toContain('123');
  });
});

Практический пример: интернет-магазин

// State структура
const storeState = {
  products: {
    byId: {
      '1': { id: '1', name: 'Товар 1', price: 100 },
      '2': { id: '2', name: 'Товар 2', price: 200 }
    },
    allIds: ['1', '2'],
    loading: false,
    error: null
  },
  cart: {
    items: ['1', '2'],
    total: 300
  },
  filters: {
    category: 'electronics',
    priceRange: [0, 1000],
    sort: 'price-asc'
  }
};

// Компоненты просто подписываются
function ProductCard({ productId }) {
  const product = useSelector(state => state.products.byId[productId]);
  const inCart = useSelector(state => state.cart.items.includes(productId));
  const dispatch = useDispatch();
  
  return (
    <div>
      <h3>{product.name}</h3>
      <p>${product.price}</p>
      <button 
        onClick={() => dispatch({ type: 'ADD_TO_CART', payload: productId })}
      >
        {inCart ? 'В корзине' : 'Добавить'}
      </button>
    </div>
  );
}

Когда НЕ использовать State Manager

  • Очень маленькие приложения (простой сайт-визитка)
  • Данные, которые используются только в одном компоненте
  • Временные UI состояния (открыт ли modal, скрыт ли sidebar)
  • Данные, которые частто меняются локально

Для этого лучше использовать обычный useState, Context API или локальное состояние компонента.

Вывод

Хранение карточек товара в State Manager - это архитектурное решение, которое обеспечивает:

  • Единственный источник истины
  • Синхронизацию между компонентами
  • Кэширование и оптимизацию
  • Нормализацию данных
  • Отслеживание состояния
  • Лучшую отладку и тестирование

Это критично для любого приложения среднего и большого размера.

Зачем в State Manager класть карточки товара? | PrepBro