Здравствуйте! Когда дело доходит до приложений электронной коммерции, единственное, чего мы не можем избежать — это управление состоянием с функцией «добавить в корзину»,
Сегодня я собираюсь показать вам, как построить его, а также оптимальный способ сделать это без использования Redux, который я вижу люди используют много, он поставляется вместе с большим количеством котельных пластин, которые вы хотели бы избежать любой ценой.
Все дело в оптимизации 😉 Давайте отправимся в путь.
- Структура папки
- внутренняя папка pages/
- Главная страница pages/index.js
- Настройка продуктов
- Разогрев
- Давайте посмотрим, что у нас получилось.
- Ура!!! 🥳
- Посмотрим, что у нас получилось
- Переходим к реальной работе
- Настройка useContext
- Настройка объекта reducer и state
- Доступ к данным состояния из файла pages/index.js
- Наконец
Структура папки
внутренняя папка pages/
Главная страница pages/index.js
Ваша домашняя страница должна выглядеть примерно так в первый раз
import Head from 'next/head'
import Image from 'next/image'
import styles from '../styles/Home.module.css'
export default function Home() {
return (
<div className={styles.container}>
<Head>
<title>Product Cart System</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<h1>this is our home page 🥺</h1>
</div>
)
}
Настройка продуктов
Теперь давайте создадим фиктивные товары, которые можно будет добавлять в нашу
корзину
import Head from 'next/head'
import Image from 'next/image'
import styles from '../styles/Home.module.css'
const DummyProducts = [
{
id: 1,
name: 'product1',
price: 10,
image: 'https://images.unsplash.com/photo-1518791841217-8f162f1e1131?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=60',
description: 'this is a dummy product description'
},
{
id: 2,
name: 'product2',
price: 20,
image: 'https://images.unsplash.com/photo-1518791841217-8f162f1e1131?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=60',
description: 'this is a dummy product description'
},
{
id: 3,
name: 'product3',
price: 30,
image: 'https://images.unsplash.com/photo-1518791841217-8f162f1e1131?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=60',
description: 'this is a dummy product description'
},
{
id: 4,
name: 'product4',
price: 40,
image: 'https://images.unsplash.com/photo-1518791841217-8f162f1e1131?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=60',
description: 'this is a dummy product description'
},
]
...
Разогрев
Хорошо, мы установили переменную DummyProducts с 4 объектами в массиве, но мы ничего не увидим, пока не сопоставим каждый объект и не отобразим отдельный элемент в массиве ‘DummyProducts’ на экране.
...
const DummyProducts = [...] // contains a lot of data 😮💨
export default function Home() {
return (
<div className={styles.container}>
<Head>
<title>Product Cart System</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<h1 className={styles.heading}>Cart Quantity 🛒 (0)</h1>
<div className={styles.products}>
{DummyProducts.map(product => (
<div className={styles.product} key={product.id}>
<Image src={product.image} width={200} height={200} placeholder={'blur'} blurDataURL={product.image} />
<h3 className={styles.name}>{product.name}</h3>
<p className={styles.description}>{product.description}</p>
<h4 className={styles.price}>${product.price}</h4>
<button className={styles.addToCart}>Add to cart</button>
</div>
))}
</div>
</div>
)
}
Давайте посмотрим, что у нас получилось.
Да, для Next.js при использовании внешнего url во встроенном компоненте next’s image нам необходимо явно указать
домен url в файле next.config.js
, вот как это делается
как это делается
inside the next.config.js file
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
images: {
domains: ['images.unsplash.com'], // <--add this
},
}
module.exports = nextConfig
вот как это просто делается 😁 , теперь давайте посмотрим, что у нас на экране
Ура!!! 🥳
Но подождите, наша страница выглядит некрасиво, давайте добавим ей немного стилизации.
внутри styles/Home.module.css
.container {
padding: 0 2rem;
}
.products{
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
grid-gap: 2rem;
position: relative;
z-index: 2;
width: 70%;
align-self: center;
margin:0 auto;
border:1px solid rgb(193, 193, 193);
padding:30px;
background-color: #fff;
margin-top:100px;
}
.heading{
font-size:2rem;
font-weight:bold;
text-align:center;
margin-bottom:30px;
padding:20px;
}
.name{
font-size:1.5rem;
font-weight:bold;
text-align:center;
}
.description{
font-size:0.9rem;
text-align:center;
}
.price{
font-size:1.2rem;
font-weight:bold;
text-align:center;
color: green;
}
.addToCart{
width:100%;
padding:14px;
background-color: #000000;
color: white;
border: none;
cursor: pointer;
}
.addToCart:hover{
background-color: rgb(69, 69, 69);
color: #ffffff;
}
Посмотрим, что у нас получилось
Теперь это минимально 😎
Переходим к реальной работе
мы закончили с визуализацией
переходим к реальной работе
Настройка useContext
Хук «useContext» используется для создания общих данных, которые могут быть доступны по всей иерархии компонентов без передачи реквизитов вручную на каждый уровень.
Для следующего js мы переходим в корень нашего приложения и устанавливаем там контекст, чтобы данные были доступны по всему нашему приложению.
внутри pages/_app.js
.
import '../styles/globals.css'
import React, { createContext } from 'react'
export const CartSystem = createContext()
function MyApp({ Component, pageProps }) {
return (
<CartSystem.Provider value={{}}>
<Component {...pageProps} />
</CartSystem.Provider>
)
}
export default MyApp
Вот так, наш useContext устанавливается в экспортируемую переменную CartSystem
и имеет готовый Provider
с неопределенным значением, и именно здесь мы устанавливаем наш reducer и значение state.
Настройка объекта reducer и state
нам понадобится только одно состояние, которым будет cart
с начальным значением в виде пустого массива
Для простоты наши reducer и state будут находиться в одном файле.
import '../styles/globals.css'
import React, { createContext,useReducer } from 'react'
export const CartSystem = createContext()
const initailState = {
cart: []
}
function MyApp({ Component, pageProps }) {
const Reducers = ()=>{
}
const [state,dispatch] = useReducer(Reducers,initailState)
return (
<CartSystem.Provider value={{}}>
<Component {...pageProps} />
</CartSystem.Provider>
)
}
export default MyApp
Теперь это обновление нашего файла pages/_app.js
.
была создана структура функции Reducer
, а также объект initialState
.
Хук useReducer
возвращает два значения, dispatch
и state
.
Я дам ссылку, чтобы прочитать больше о хуке useReducer, если вы новичок в этом.
Теперь давайте передадим эти данные как значение через наш контекст, чтобы
наше приложение могло получить доступ ко всем данным в любом месте.
...
return (
<CartSystem.Provider value={{state,dispatch}}>
<Component {...pageProps} />
</CartSystem.Provider>
)
...
С помощью этого мы можем получить доступ к тому, что находится в состоянии в любом компоненте нашего приложения.
Доступ к данным состояния из файла pages/index.js
Чтобы получить доступ к данным со страницы нашего продукта, нам нужно использовать функцию
import Head from 'next/head'
import Image from 'next/image'
import styles from '../styles/Home.module.css'
import React,{useContext} from 'react'
import {CartSystem} from './_app'
const DummyProducts = [...]
export default function Home() {
const {state,dispatch} = useContext(CartSystem)
return (
<div className={styles.container}>
<Head>
<title>Product Cart System</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<h1 className={styles.heading}>Cart Quantity 🛒 (0)</h1>
<div className={styles.products}>
{DummyProducts.map(product => (
...
javascript
мы импортируем useContext
и CartSystem
из react
и _app.js
соответственно.
Затем мы получаем данные с помощью хука useContext, передавая параметр
Поскольку наше начальное состояние — это пустой массив, мы не сможем ничего из него отобразить, попробуем создать функцию для добавления товаров в корзину.
...
export default function Home() {
const {state,dispatch} = useContext(CartSystem)
const addToCart =(product)=>{
dispatch({type:'ADD_TO_CART',payload:product})
}
return (
...
Мы создаем функцию addToCart
и назначаем диспетчер с типом действия 'ADD_TO_CART'
и полезной нагрузкой элемента, который будет выбран, теперь внутри нашего редуктора в файле pages/_app.js
создадим действие 'ADD_TO_CART'
, чтобы наша функция могла быть реализована.
...
function MyApp({ Component, pageProps }) {
const Reducers = (state,action)=>{
switch(action.type){
case 'ADD_TO_CART':
const {id, name, price,description} = action.payload
const cartItem = state.cart.find(item => item.id === id)
if (cartItem) {
return {
...state,
cart: state.cart.map(item => item.id === id ? {...item, quantity: item.quantity + 1} : item)
}
} else {
return {
...state,
cart: [...state.cart, {id, name, price, description, quantity: 1}]
}
}
default:
return state;
}
}
const [state,dispatch] = useReducer(Reducers,initailState)
...
теперь функция reducer принимает два параметра state
и action
, мы используем оператор switch для проверки действия и выполнения задачи на основе действия, в нашем случае мы слушаем действие ADD_TO_CART
.
Теперь, когда функция ADD_TO_CART
установлена, давайте реализуем это действие на нашей кнопке Добавить в корзину и посмотрим, что у нас получилось
import Head from 'next/head'
import Image from 'next/image'
import styles from '../styles/Home.module.css'
import React,{useContext,useEffect} from 'react'
import {CartSystem} from './_app'
const DummyProducts = [...]
export default function Home() {
const {state,dispatch} = useContext(CartSystem)
const addToCart =(product)=>{
dispatch({type:'ADD_TO_CART',payload:product})
}
// add all the products price in cart
const total = state.cart.reduce((total,item)=>{
return total + item.price * item.quantity
},0)
// add all product quantity items
const totalItems = state.cart.reduce((total,item)=>{
return total + item.quantity
},0)
return (
<div className={styles.container}>
<Head>
<title>Product Cart System</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<h1 className={styles.heading}>Cart Quantity 🛒 ({totalItems}) | Total Price 💰 $({total})</h1>
<div className={styles.products}>
{DummyProducts.map(product => (
<div onClick={()=>addToCart(product)} className={styles.product} key={product.id}>
<Image src={product.image} width={200} height={200} placeholder={'blur'} blurDataURL={product.image} />
<h3 className={styles.name}>{product.name}</h3>
<p className={styles.description}>{product.description}</p>
<h4 className={styles.price}>${product.price}</h4>
<button className={styles.addToCart}>Add to cart</button>
</div>
))}
</div>
</div>
)
}
Наконец
теперь мы сделали некоторые изменения в нашем файле pages/index
Я добавил две функции, которые находили общее количество и общую цену товаров
затем добавил функцию addToCart
к кнопке добавления в корзину, и теперь у нас есть полностью рабочая система добавления в корзину.
* Теперь мы создали минимальную систему добавления в корзину без установки каких-либо библиотек или использования redux, наш код все еще находится в минимальном состоянии, та же цель может быть достигнута с помощью redux, но зачем напрягаться? *
дайте мне знать, если вы хотите получить полное продолжение этого урока
через комментарииниже приведена ссылка на полный репозиторий приложения
Полный репозиторий проекта