E-commerce — комплексный проект уровня Junior+. Демонстрирует полный цикл разработки интернет-магазина.
Что вы создадите:
1. Каталог товаров
- Grid/List переключение
- Фильтры: категория, цена, бренд
- Сортировка: цена, популярность, новинки
- Пагинация или infinite scroll
- Поиск
2. Карточка товара
- Галерея изображений
- Варианты (размер, цвет)
- Отзывы и рейтинг
- Похожие товары
3. Корзина
- Добавление/удаление
- Изменение количества
- Расчёт итоговой суммы
- Промокоды
4. Оформление заказа
- Форма доставки
- Способ оплаты
- Подтверждение заказа
src/
├── pages/
│ ├── Home.jsx
│ ├── Catalog.jsx
│ ├── Product.jsx
│ ├── Cart.jsx
│ ├── Checkout.jsx
│ └── Account/
├── components/
│ ├── ProductCard.jsx
│ ├── ProductGrid.jsx
│ ├── Filters.jsx
│ ├── CartItem.jsx
│ └── ...
├── store/ # Redux или Zustand
│ ├── cartSlice.js
│ ├── productsSlice.js
│ └── userSlice.js
├── hooks/
│ ├── useCart.js
│ └── useProducts.js
└── api/
└── products.js{
id: 'prod_123',
name: 'Кроссовки Nike Air Max',
slug: 'nike-air-max',
description: '...',
price: 12990,
oldPrice: 15990, // для скидки
images: ['url1', 'url2'],
category: 'shoes',
brand: 'Nike',
variants: [
{ size: '42', color: 'black', stock: 5 },
{ size: '43', color: 'black', stock: 3 }
],
rating: 4.5,
reviewsCount: 128,
tags: ['new', 'sale']
}{
productId: 'prod_123',
variantId: 'var_456', // size + color
quantity: 2,
price: 12990,
name: '...',
image: '...'
}{
id: 'order_789',
items: [...],
total: 25980,
status: 'processing',
shipping: { name, phone, address },
payment: { method: 'card' },
createdAt: '...'
}const addToCart = async (product) => {
// Сразу обновляем UI
dispatch(cartActions.addItem(product))
try {
// Потом синхронизируем с сервером
await api.addToCart(product.id)
} catch {
// Откатываем при ошибке
dispatch(cartActions.removeItem(product.id))
}
}// URL: /catalog?category=shoes&minPrice=5000&sort=price_asc
const [searchParams, setSearchParams] = useSearchParams()
const filters = {
category: searchParams.get('category'),
minPrice: searchParams.get('minPrice'),
sort: searchParams.get('sort')
}const debouncedSearch = useMemo(
() => debounce((query) => {
setSearchParams({ q: query })
}, 300),
[]
)E-commerce: корзина с хуками и localStorage
// Custom Hook для корзины e-commerce
function useCart() {
const [items, setItems] = React.useState(() => {
const saved = localStorage.getItem('cart')
return saved ? JSON.parse(saved) : []
})
// Сохранение в localStorage
React.useEffect(() => {
localStorage.setItem('cart', JSON.stringify(items))
}, [items])
// Добавление товара
const addItem = (product, variant = null) => {
setItems(prev => {
const key = variant ? product.id + '-' + variant.id : product.id
const existing = prev.find(item => item.key === key)
if (existing) {
return prev.map(item =>
item.key === key
? { ...item, quantity: item.quantity + 1 }
: item
)
}
return [...prev, {
key,
productId: product.id,
variantId: variant?.id,
name: product.name,
price: product.price,
image: product.image,
variant: variant?.name,
quantity: 1
}]
})
}
// Обновление количества
const updateQuantity = (key, quantity) => {
if (quantity < 1) {
removeItem(key)
return
}
setItems(prev =>
prev.map(item =>
item.key === key ? { ...item, quantity } : item
)
)
}
// Удаление товара
const removeItem = (key) => {
setItems(prev => prev.filter(item => item.key !== key))
}
// Очистка корзины
const clearCart = () => setItems([])
// Вычисляемые значения
const itemsCount = items.reduce((sum, item) => sum + item.quantity, 0)
const subtotal = items.reduce((sum, item) => sum + item.price * item.quantity, 0)
const shipping = subtotal > 5000 ? 0 : 500
const total = subtotal + shipping
return {
items,
addItem,
updateQuantity,
removeItem,
clearCart,
itemsCount,
subtotal,
shipping,
total
}
}
// === Демонстрация ===
console.log('=== E-commerce Cart Demo ===\n')
// Симуляция хука
let cartState = []
const setCart = (fn) => { cartState = typeof fn === 'function' ? fn(cartState) : fn }
const addToCart = (product) => {
setCart(prev => {
const existing = prev.find(item => item.productId === product.id)
if (existing) {
return prev.map(item =>
item.productId === product.id
? { ...item, quantity: item.quantity + 1 }
: item
)
}
return [...prev, { ...product, productId: product.id, quantity: 1 }]
})
console.log('🛒 Добавлено:', product.name)
}
// Товары
const products = [
{ id: 1, name: 'React Книга', price: 1500, image: '📕' },
{ id: 2, name: 'TypeScript Курс', price: 3500, image: '💻' },
{ id: 3, name: 'Node.js Гайд', price: 2000, image: '📗' },
]
// Добавляем товары
addToCart(products[0])
addToCart(products[1])
addToCart(products[0]) // Повторно — увеличит quantity
console.log('\nКорзина:')
cartState.forEach(item => {
console.log(` ${item.image} ${item.name} x${item.quantity} = ${item.price * item.quantity}₽`)
})
const total = cartState.reduce((sum, item) => sum + item.price * item.quantity, 0)
console.log('\nИтого:', total, '₽')E-commerce — комплексный проект уровня Junior+. Демонстрирует полный цикл разработки интернет-магазина.
Что вы создадите:
1. Каталог товаров
- Grid/List переключение
- Фильтры: категория, цена, бренд
- Сортировка: цена, популярность, новинки
- Пагинация или infinite scroll
- Поиск
2. Карточка товара
- Галерея изображений
- Варианты (размер, цвет)
- Отзывы и рейтинг
- Похожие товары
3. Корзина
- Добавление/удаление
- Изменение количества
- Расчёт итоговой суммы
- Промокоды
4. Оформление заказа
- Форма доставки
- Способ оплаты
- Подтверждение заказа
src/
├── pages/
│ ├── Home.jsx
│ ├── Catalog.jsx
│ ├── Product.jsx
│ ├── Cart.jsx
│ ├── Checkout.jsx
│ └── Account/
├── components/
│ ├── ProductCard.jsx
│ ├── ProductGrid.jsx
│ ├── Filters.jsx
│ ├── CartItem.jsx
│ └── ...
├── store/ # Redux или Zustand
│ ├── cartSlice.js
│ ├── productsSlice.js
│ └── userSlice.js
├── hooks/
│ ├── useCart.js
│ └── useProducts.js
└── api/
└── products.js{
id: 'prod_123',
name: 'Кроссовки Nike Air Max',
slug: 'nike-air-max',
description: '...',
price: 12990,
oldPrice: 15990, // для скидки
images: ['url1', 'url2'],
category: 'shoes',
brand: 'Nike',
variants: [
{ size: '42', color: 'black', stock: 5 },
{ size: '43', color: 'black', stock: 3 }
],
rating: 4.5,
reviewsCount: 128,
tags: ['new', 'sale']
}{
productId: 'prod_123',
variantId: 'var_456', // size + color
quantity: 2,
price: 12990,
name: '...',
image: '...'
}{
id: 'order_789',
items: [...],
total: 25980,
status: 'processing',
shipping: { name, phone, address },
payment: { method: 'card' },
createdAt: '...'
}const addToCart = async (product) => {
// Сразу обновляем UI
dispatch(cartActions.addItem(product))
try {
// Потом синхронизируем с сервером
await api.addToCart(product.id)
} catch {
// Откатываем при ошибке
dispatch(cartActions.removeItem(product.id))
}
}// URL: /catalog?category=shoes&minPrice=5000&sort=price_asc
const [searchParams, setSearchParams] = useSearchParams()
const filters = {
category: searchParams.get('category'),
minPrice: searchParams.get('minPrice'),
sort: searchParams.get('sort')
}const debouncedSearch = useMemo(
() => debounce((query) => {
setSearchParams({ q: query })
}, 300),
[]
)E-commerce: корзина с хуками и localStorage
// Custom Hook для корзины e-commerce
function useCart() {
const [items, setItems] = React.useState(() => {
const saved = localStorage.getItem('cart')
return saved ? JSON.parse(saved) : []
})
// Сохранение в localStorage
React.useEffect(() => {
localStorage.setItem('cart', JSON.stringify(items))
}, [items])
// Добавление товара
const addItem = (product, variant = null) => {
setItems(prev => {
const key = variant ? product.id + '-' + variant.id : product.id
const existing = prev.find(item => item.key === key)
if (existing) {
return prev.map(item =>
item.key === key
? { ...item, quantity: item.quantity + 1 }
: item
)
}
return [...prev, {
key,
productId: product.id,
variantId: variant?.id,
name: product.name,
price: product.price,
image: product.image,
variant: variant?.name,
quantity: 1
}]
})
}
// Обновление количества
const updateQuantity = (key, quantity) => {
if (quantity < 1) {
removeItem(key)
return
}
setItems(prev =>
prev.map(item =>
item.key === key ? { ...item, quantity } : item
)
)
}
// Удаление товара
const removeItem = (key) => {
setItems(prev => prev.filter(item => item.key !== key))
}
// Очистка корзины
const clearCart = () => setItems([])
// Вычисляемые значения
const itemsCount = items.reduce((sum, item) => sum + item.quantity, 0)
const subtotal = items.reduce((sum, item) => sum + item.price * item.quantity, 0)
const shipping = subtotal > 5000 ? 0 : 500
const total = subtotal + shipping
return {
items,
addItem,
updateQuantity,
removeItem,
clearCart,
itemsCount,
subtotal,
shipping,
total
}
}
// === Демонстрация ===
console.log('=== E-commerce Cart Demo ===\n')
// Симуляция хука
let cartState = []
const setCart = (fn) => { cartState = typeof fn === 'function' ? fn(cartState) : fn }
const addToCart = (product) => {
setCart(prev => {
const existing = prev.find(item => item.productId === product.id)
if (existing) {
return prev.map(item =>
item.productId === product.id
? { ...item, quantity: item.quantity + 1 }
: item
)
}
return [...prev, { ...product, productId: product.id, quantity: 1 }]
})
console.log('🛒 Добавлено:', product.name)
}
// Товары
const products = [
{ id: 1, name: 'React Книга', price: 1500, image: '📕' },
{ id: 2, name: 'TypeScript Курс', price: 3500, image: '💻' },
{ id: 3, name: 'Node.js Гайд', price: 2000, image: '📗' },
]
// Добавляем товары
addToCart(products[0])
addToCart(products[1])
addToCart(products[0]) // Повторно — увеличит quantity
console.log('\nКорзина:')
cartState.forEach(item => {
console.log(` ${item.image} ${item.name} x${item.quantity} = ${item.price * item.quantity}₽`)
})
const total = cartState.reduce((sum, item) => sum + item.price * item.quantity, 0)
console.log('\nИтого:', total, '₽')Создай мини e-commerce: каталог товаров с фильтром по категории, корзину с изменением количества, и простой checkout. Используй localStorage для сохранения корзины.
localStorage: JSON.stringify(cart). filter: item.quantity > 0 (удаляем товары с нулевым количеством).