Изготовление сайтов в Дружковке, ДНР. Как создать сайт электронной коммерции с React

 
 

В этом руководстве мы рассмотрим, как создать очень простое веб-приложение электронной коммерции с помощью React. Это не будет следующий Shopify, но, надеюсь, он послужит забавным способом продемонстрировать, насколько хорошо React подходит для создания динамичных и привлекательных пользовательских интерфейсов.

Приложение продемонстрирует базовую систему управления корзиной, а также простой метод обработки аутентификации пользователей. Мы будем использовать React Context в качестве альтернативы фреймворкам управления состоянием, таким как Redux или MobX, и создадим фальшивую серверную часть с помощью пакета json-server.

Ниже скриншот того, что мы будем строить:

Готовое приложение

Код этого приложения доступен на GitHub.

Предпосылки

В этом руководстве предполагается, что у вас есть базовые знания JavaScript и React. Если вы новичок в React, вы можете ознакомиться с нашим руководством для начинающих.

Для создания приложения вам понадобится последняя версия Node, установленная на вашем ПК. Если это не так, перейдите на домашнюю страницу Node и загрузите правильные двоичные файлы для вашей системы. В качестве альтернативы вы можете рассмотреть возможность использования диспетчера версий для установки Node. У нас есть туториал по использованию менеджера версий.

Node поставляется в комплекте с npm, менеджером пакетов для JavaScript, с помощью которого мы собираемся установить некоторые библиотеки, которые будем использовать. Вы можете узнать больше об использовании npm здесь.

Вы можете проверить правильность установки обоих, выполнив следующие команды из командной строки:

node -v

> 12.18.4

npm -v

> 6.14.8

Сделав это, давайте начнем с создания нового проекта React с помощью инструмента Create React App. Вы можете либо установить это глобально, либо использовать npx, например:

npx create-react-appe-commerce

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

cd e-commerce

В этом приложении мы будем использовать React Router для обработки маршрутизации. Чтобы установить этот модуль, запустите:

npm install react-router-dom

Нам также понадобятся json-server и json-server-auth, чтобы создать фальшивую серверную часть для обработки аутентификации:

npm install json-serverjson-server-auth

Нам понадобятся axios для выполнения запросов Ajax к нашему поддельному серверу.

npm install axios

И нам понадобится jwt-decode, чтобы мы могли проанализировать JWT, который ответит наш сервер:

npm install jwt-decode

Наконец, мы будем использовать фреймворк Bulma CSS для стилизации этого приложения. Чтобы установить это, выполните следующую команду:

npm install bulma

Начиная

Во-первых, нам нужно добавить таблицу стилей в наше приложение. Для этого мы добавим оператор импорта, чтобы включить этот файл в index.jsфайл в srcпапке. Это применит таблицу стилей ко всем компонентам в приложении:

import «bulma/css/bulma.css»;

Настройка контекста

Как упоминалось ранее, мы будем использовать React Context во всем нашем приложении. Это относительно новое дополнение к React, которое позволяет передавать данные через дерево компонентов без необходимости вручную передавать свойства на каждом уровне.

Если вы хотите освежить в памяти использование контекста в приложении React, ознакомьтесь с нашим руководством «Как заменить Redux на React Hooks и Context API «.

В сложных приложениях, где обычно необходим контекст, может быть несколько контекстов, каждый из которых имеет свои собственные данные и методы, относящиеся к набору компонентов, для которых требуются данные и методы. Например, один может использоваться ProductContextдля обработки компонентов, использующих данные о продукте, а другой ProfileContext— для обработки данных, связанных с аутентификацией и пользовательскими данными. Однако, чтобы все было как можно проще, мы будем использовать только один экземпляр контекста.

Чтобы создать контекст, мы создаем Context.jsфайл и withContext.jsфайлы в каталоге нашего приложения src:

cd src

touch Context.js withContext.js

Затем добавьте следующее Context.js:

import React from «react»;

const Context = React.createContext ({}) ;

export default Context;

Это создает контекст и инициализирует данные контекста пустым объектом. Далее нам нужно создать оболочку компонента, которую мы будем использовать для обертывания компонентов, использующих данные и методы контекста:

// src/withContext.js

import React from «react»;

import Context from «. /Context»;

const withContext = WrappedComponent => {

const WithHOC = props => {

return (

{context => }

 

) ;

};

return WithHOC;

};

export default withContext;

Это может показаться немного сложным, но, по сути, все, что он делает, это создает компонент более высокого порядка, который добавляет наш контекст к свойствам обернутого компонента.

Немного разбив его, мы видим, что withContextфункция принимает компонент React в качестве параметра. Затем он возвращает функцию, которая принимает свойства компонента в качестве параметра. В возвращаемой функции мы оборачиваем компонент в наш контекст, а затем назначаем ему контекст в качестве реквизита: context={context}. Этот {...props}бит гарантирует, что компонент сохранит все реквизиты, которые были переданы ему в первую очередь.

Все это означает, что мы можем следовать этому шаблону во всем нашем приложении:

import React from «react»;

import withContext from «.../withContext»;

const Cart = props => {

// We can now access Context as props.context

};

export default withContext (Cart) ;

Создание шаблонов приложения

Теперь давайте создадим скелетную версию компонентов, которые нам понадобятся для правильной работы базовой навигации нашего приложения. Это AddProducts, Cart, Loginи ProductList, и мы собираемся поместить их в componentsкаталог внутри srcкаталога:

mkdir components

cd components

touch AddProduct.js Cart.js Login.js ProductList.js

В AddProduct.jsдобавление:

import React from «react»;

export default function AddProduct () {

return <>AddProduct</>

}

В Cart.jsдобавление:

import React from «react»;

export default function Cart () {

return <>Cart</>

}

В Login.jsдобавление:

import React from «react»;

export default function Login () {

return <>Login</>

}

И, наконец, в ProductList.jsдополнение:

import React from «react»;

export default function ProductList () {

return <>ProductList</>

}

Далее нам нужно настроить App.jsфайл. Здесь мы будем обрабатывать навигацию приложения, а также определять его данные и методы для управления им.

Для начала настроим навигацию. Измените App.jsследующим образом:

import React, { Component } from «react»;

import { Switch, Route, Link, BrowserRouter as Router } from «react-router-dom»;

import AddProduct from '. /components/AddProduct’;

import Cart from '. /components/Cart’;

import Login from '. /components/Login’;

import ProductList from '. /components/ProductList’;

import Context from «. /Context»;

export default class App extends Component {

constructor (props) {

super (props) ;

this.state = {

user: null,

cart: {},

products: []

};

this.routerRef = React.createRef () ;

}

render () {

return (

<Context.Provider

value={{

...this.state,

removeFromCart: this.removeFromCart,

addToCart: this.addToCart,

login: this.login,

addProduct: this.addProduct,

clearCart: this.clearCart,

checkout: this.checkout

}}

>

 

 

<nav

className="navbar container"

role="navigation"

aria-label="main navigation"

>

 

 

ecommerce

<label

role="button"

class="navbar-burger burger"

aria-label="menu"

aria-expanded="false"

data-target="navbarBasicExample"

onClick={e => {

e.preventDefault () ;

this.setState ({ showMenu:! this.state.showMenu }) ;

}}

>

aria-hidden="true">

aria-hidden="true">

aria-hidden="true">

 

 

 

<div className={`navbar-menu ${

this.state.showMenu? «is-active»: «»

}`}>

 

Products

 

{this.state.user && this.state.user.accessLevel < 1 && (

 

Add Product

 

) }

 

Cart

<span

className="tag is-primary"

style={{ marginLeft: «5px» }}

>

{ Object.keys (this.state.cart).length }

 

 

{! this.state.user? (

 

Login

 

): (

 

Logout

 

) }

 

 

 

 

 

 

 

) ;

}

}

Наш Appкомпонент будет отвечать за инициализацию данных приложения, а также определять методы для управления этими данными. Во-первых, мы определяем контекстные данные и методы, используя Context.Providerкомпонент. Данные и методы передаются как свойство компонента valueдля Providerзамены объекта, заданного при создании контекста. (Обратите внимание, что значение может относиться к любому типу данных.) Мы передаем значение состояния и некоторые методы, которые мы вскоре определим.

Далее мы создаем навигацию по нашему приложению. Для этого нам нужно обернуть наше приложение Routerкомпонентом, который может быть BrowserRouter (как в нашем случае) или HashRouter. Затем мы определяем маршруты нашего приложения с помощью компонентов Switchи. RouteМы также создаем меню навигации приложения, где каждая ссылка использует Linkкомпонент, предоставленный в модуле React Router. Мы также добавляем ссылку routerRefна Routerкомпонент, чтобы мы могли получить доступ к маршрутизатору из Appкомпонента.

Чтобы проверить это, перейдите в корень проекта (например, /files/jim/Desktop/e-commerce) и запустите сервер разработки Create React App, используя npm start. После загрузки должен открыться браузер по умолчанию, и вы должны увидеть скелет нашего приложения. Обязательно пощелкайте и убедитесь, что вся навигация работает.

Создание поддельного бэкэнда

На следующем шаге мы настроим фальшивую серверную часть для хранения наших продуктов и обработки аутентификации пользователей. Как уже упоминалось, для этого мы будем использовать json-server для создания поддельного REST API и json-server-auth для добавления простого потока аутентификации на основе JWT в наше приложение.

Работа json-server заключается в том, что он считывает файл JSON из файловой системы и использует его для создания базы данных в памяти с соответствующими конечными точками для взаимодействия с ней. Давайте сейчас создадим файл JSON. В маршруте вашего проекта создайте новую backendпапку и в этой папке создайте новый db.jsonфайл:

mkdir backend

cd backend

touch db.json

Откройте db.jsonи добавьте следующее содержимое:

{

«users»: [

{

«email»: «regular@example.com»,

«password»: «$2a$$102myKMolZJoH.q.cyXClQXufY1Mc7ETKdSaQQCC6Fgtbe0DCXRBELG»,

«id»: 1

},

{

«email»: «admin@example.com»,

«password»: «$2a$$10w8qB40MdYkMs3dgGGf0Pu.xxVOOzWdZ5/Nrkleo3Gqc88PF/OQhOG»,

«id»: 2

}

],

«products»: [

{

«id»: «hdmdu0t80yjkfqselfc»,

«name»: «shoes»,

«stock»: 10,

«price»: 399.99,

«shortDesc»: «Nulla facilisi. Curabitur at lacus ac velit ornare lobortis.»,

«description»: «Cras sagittis. Praesent nec nisl a purus blandit viverra. Ut leo. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Fusce a quam.»

},

{

«id»: «3dc7fiyzlfmkfqseqam»,

«name»: «bags»,

«stock»: 20,

«price»: 299.99,

«shortDesc»: «Nulla facilisi. Curabitur at lacus ac velit ornare lobortis.»,

«description»: «Cras sagittis. Praesent nec nisl a purus blandit viverra. Ut leo. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Fusce a quam.»

},

{

«id»: «aoe8wvdxvrkfqsew67»,

«name»: «shirts»,

«stock»: 15,

«price»: 149.99,

«shortDesc»: «Nulla facilisi. Curabitur at lacus ac velit ornare lobortis.»,

«description»: «Cras sagittis. Praesent nec nisl a purus blandit viverra. Ut leo. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Fusce a quam.»

},

{

«id»: «bmfrurdkswtkfqsf15j»,

«name»: «shorts»,

«stock»: 5,

«price»: 109.99,

«shortDesc»: «Nulla facilisi. Curabitur at lacus ac velit ornare lobortis.»,

«description»: «Cras sagittis. Praesent nec nisl a purus blandit viverra. Ut leo. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Fusce a quam.»

}

]

}

Здесь мы создаем два ресурса — usersи products. Глядя на usersресурс, вы заметите, что у каждого пользователя есть идентификатор, адрес электронной почты и пароль. Пароль выглядит как набор букв и цифр, так как он зашифрован с помощью bcryptjs. Важно, чтобы вы не хранили пароли в виде простого текста в любом месте вашего приложения.

Тем не менее, текстовая версия каждого пароля — это просто «пароль» — без кавычек.

Теперь запустите сервер, выполнив следующую команду из корня проекта:

. /node_modules/.bin/json-server-auth. /backend/db.json —port 3001

Это запустит json-сервер на http: //localhost:3001. Благодаря промежуточному программному обеспечению json-server-auth usersресурс также предоставит нам /loginконечную точку, которую мы можем использовать для имитации входа в приложение.

Давайте попробуем это с помощью https://hoppscotch.io. Откройте эту ссылку в новом окне, затем измените метод POSTи URL-адрес на http: //localhost:3001/login. Затем убедитесь, что переключатель ввода Raw включен, и введите следующее в качестве тела запроса Raw:

{

«email»: «regular@example.com»,

«password»: «password»

}

Нажмите «Отправить «, и вы должны получить ответ (ниже на странице), который выглядит следующим образом:

{

«accessToken»: «eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InJlZ3VsYXJAZXhhbXBsZS5jb20iLCJpYXQiOjE2MDE1Mzk3NzEsImV4cCI6MTYwMTU0MzM3MSwic3ViIjoiMSJ9.RAFUYXxG2Z8W8zv5−4OHun8CmCKqi7IYqYAc4R7STBM»

}

Это веб-токен JSON, действительный в течение часа. В обычном приложении с надлежащей серверной частью вы должны сохранить это в клиенте, а затем отправлять его на сервер всякий раз, когда запрашиваете защищенный ресурс. Сервер проверит полученный токен и, если все будет проверено, ответит запрошенными вами данными.

Этот момент стоит повторить. Вам необходимо подтвердить любой запрос на защищенный ресурс на вашем сервере. Это связано с тем, что код, который выполняется в клиенте, потенциально может быть реконструирован и изменен.

Вот ссылка на готовый запрос на Hoppscotch. Вам просто нужно нажать Отправить.

Если вы хотите узнать больше об использовании веб-токенов JSON с Node.js, обратитесь к нашему руководству.

Реализация аутентификации в приложении React

Для этого раздела нам понадобятся пакеты axios и jwt_decode в нашем приложении. Добавьте импорт в начало App.jsфайла:

import axios from ‘axios’;

import jwt_decode from ‘jwt-decode’;

Если вы посмотрите на верхнюю часть класса, вы увидите, что мы уже объявляем пользователя в состоянии. Изначально установлено значение null.

Затем нам нужно убедиться, что пользователь загружается при запуске приложения, настроив пользователя на монтирование компонента, как показано ниже. Добавьте этот метод в Appкомпонент, который загружает последнюю пользовательскую сессию из локального хранилища в состояние, если оно существует:

componentDidMount () {

let user = localStorage.getItem («user») ;

user = user? JSON.parse (user): null;

this.setState ({ user }) ;

}

Далее мы определяем методы loginи logout, которые привязаны к контексту:

login = async (email, password) => {

const res = await axios.post (

‘http: //localhost:3001/login’,

{ email, password },

).catch ((res) => {

return { status: 401, message: ‘Unauthorized’ }

})

if (res.status === 200) {

const { email } = jwt_decode (res.data.accessToken)

const user = {

email,

token: res.data.accessToken,

accessLevel: email === ‘admin@example.com’? 0: 1

}

this.setState ({ user }) ;

localStorage.setItem («user», JSON.stringify (user));

return true;

} else {

return false;

}

}

logout = e => {

e.preventDefault () ;

this.setState ({ user: null }) ;

localStorage.removeItem («user») ;

};

Метод loginделает Ajax-запрос к нашей /loginконечной точке, передавая ему все, что пользователь ввел в форму входа (мы сделаем это через минуту). Если ответ от конечной точки имеет код состояния 200, мы можем предположить, что учетные данные пользователя были правильными. Затем мы декодируем токен, отправленный в ответе сервера, чтобы получить электронную почту пользователя, прежде чем сохранять электронную почту, токен и уровень доступа пользователя в состоянии. Если все прошло успешно, метод возвращает true, иначе false. Мы можем использовать это значение в нашем Loginкомпоненте, чтобы решить, что отображать.

Обратите внимание, что проверка уровня доступа здесь очень поверхностна, и для вошедшего в систему обычного пользователя не составит труда сделать себя администратором. Однако если предположить, что запросы на защищенные ресурсы проверяются на сервере перед отправкой ответа, пользователь не сможет сделать ничего, кроме дополнительной кнопки. Проверка сервера гарантирует, что они не смогут получить какие-либо защищенные данные.

Если вы хотите реализовать более надежное решение, вы можете сделать второй запрос, чтобы получить разрешения текущего пользователя, когда пользователь входит в систему или когда загружается приложение. К сожалению, это выходит за рамки данного руководства.

Метод logoutочищает пользователя как от состояния, так и от локального хранилища.

Создание компонента входа

Далее мы можем иметь дело с Loginкомпонентом. Этот компонент использует контекстные данные. Чтобы он имел доступ к этим данным и методам, он должен быть обернут с помощью withContextметода, который мы создали ранее.

Измените src/Login.jsтак:

import React, { Component } from «react»;

import { Redirect } from «react-router-dom»;

import withContext from «.../withContext»;

class Login extends Component {

constructor (props) {

super (props) ;

this.state = {

username: «»,

password: «»

};

}

handleChange = e => this.setState ({ [e.target.name]: e.target.value, error: «» }) ;

login = (e) => {

e.preventDefault () ;

const { username, password } = this.state;

if (! username ||! password) {

return this.setState ({ error: «Fill all fields!» }) ;

}

this.props.context.login (username, password)

.then ((loggedIn) => {

if (! loggedIn) {

this.setState ({ error: «Invalid Credentails» }) ;

}

})

};

render () {

return! this.props.context.user? (

<>

 

 

 

 

Login

 

 

 

 

 

 

 

 

 

 

 

 

 

 

<input

className="input"

type="email"

name="username"

onChange={this.handleChange}

/>

 

 

 

 

<input

className="input"

type="password"

name="password"

onChange={this.handleChange}

/>

 

 

{this.state.error && (

 

{this.state.error}

 

) }

 

 

<button

className="button is-primary is-outlined is-pulled-right"

>

Submit

 

 

 

 

 

 

 

 

 

</>

): (

) ;

}

}

export default withContext (Login) ;

Этот компонент отображает форму с двумя входными данными для сбора учетных данных пользователя. При отправке компонент вызывает loginметод, который передается через контекст. Этот модуль также обеспечивает перенаправление на страницу продуктов, если пользователь уже вошел в систему.

Если вы сейчас перейдете на http: //localhost:3000/login, вы сможете войти в систему с любой из вышеупомянутых комбинаций имя/пароль.

Страница входа с полем электронной почты и пароля

Создание представлений продукта

Теперь нам нужно получить некоторые продукты из нашей серверной части для отображения в нашем приложении. Мы снова можем сделать это при монтировании компонента в Appкомпоненте, как мы сделали для вошедшего в систему пользователя:

async componentDidMount () {

let user = localStorage.getItem («user») ;

const products = await axios.get (‘http: //localhost:3001/products’) ;

user = user? JSON.parse (user): null;

this.setState ({ user, products: products.data }) ;

}

В приведенном выше фрагменте кода мы пометили componentDidMountхук жизненного цикла как асинхронный, что означает, что мы можем сделать запрос к нашей /productsконечной точке, затем дождаться возврата данных, прежде чем вставлять их в состояние.

Затем мы можем создать страницу продуктов, которая также будет служить целевой страницей приложения. Эта страница будет использовать два компонента. Первый — это ProductList.js, который будет отображать тело страницы, а другой — ProductItem.jsкомпонент для каждого продукта в списке.

Измените Productlistкомпонент, как показано ниже:

import React from «react»;

import ProductItem from «. /ProductItem»;

import withContext from «.../withContext»;

const ProductList = props => {

const { products } = props.context;

return (

<>

 

 

 

 

Our Products

 

 

 

 

 

 

 

 

 

{products && products.length? (

products.map ((product, index) => (

<ProductItem

product={product}

key={index}

addToCart={props.context.addToCart}

/>

))

): (

 

 

 

No products found!

 

 

 

) }

 

 

 

 

</>

) ;

};

export default withContext (ProductList) ;

Поскольку список зависит от контекста данных, мы withContextтакже обертываем его функцией. Этот компонент визуализирует продукты, используя ProductItemкомпонент, который нам еще предстоит создать. Он также передает addToCartметод из контекста (который нам еще предстоит определить) в файл ProductItem. Это избавляет от необходимости работать с контекстом непосредственно в ProductItemкомпоненте.

Теперь давайте создадим ProductItemкомпонент:

cd src/components

touch ProductItem.js

И добавьте следующий контент:

import React from «react»;

const ProductItem = props => {

const { product } = props;

return (

 

 

 

 

 

 

 

 

 

 

<img

src="https://bulma.io/images/placeholders/128x128.png"

alt={product.shortDesc}

/>

 

 

 

 

 

 

 

{product.name}{» «}

${product.price}

 

 

{product.shortDesc}

 

{product.stock > 0? (

{product.stock + «Available»}

): (

Out Of Stock

) }

 

 

<button

className="button is-small is-outlined is-primary is-pulled-right"

onClick={ () =>

props.addToCart ({

id: product.name,

product,

amount: 1

})

}

>

Add to Cart

 

 

 

 

 

 

 

 

 

 

 

) ;

};

export default ProductItem;

Этот элемент отображает продукт на карточке, а также предоставляет кнопку действия для добавления продукта в корзину пользователя.

Товары

Добавление продукта

Теперь, когда у нас есть что показать в нашем магазине, давайте создадим интерфейс для администраторов, чтобы добавлять новые продукты. Во-первых, давайте определим метод добавления продукта. Мы сделаем это в Appкомпоненте, как показано ниже:

addProduct = (product, callback) => {

let products = this.state.products.slice () ;

products.push (product) ;

this.setState ({ products }, () => callback && callback ());

};

Этот метод получает productобъект и добавляет его в массив продуктов, а затем сохраняет его в состоянии приложения. Он также получает функцию обратного вызова для выполнения при успешном добавлении продукта.

Теперь можно переходить к заполнению AddProductкомпонента:

import React, { Component } from «react»;

import withContext from «.../withContext»;

import { Redirect } from «react-router-dom»;

import axios from ‘axios’;

const initState = {

name: «»,

price: «»,

stock: «»,

shortDesc: «»,

description: «»

};

class AddProduct extends Component {

constructor (props) {

super (props) ;

this.state = initState;

}

save = async (e) => {

e.preventDefault () ;

const { name, price, stock, shortDesc, description } = this.state;

if (name && price) {

const id = Math.random ().toString (36).substring (2) + Date.now ().toString (36) ;

await axios.post (

‘http: //localhost:3001/products’,

{ id, name, price, stock, shortDesc, description },

)

this.props.context.addProduct (

{

name,

price,

shortDesc,

description,

stock: stock || 0

},

() => this.setState (initState)

) ;

this.setState (

{ flash: { status: ‘is-success’, msg: ‘Product created successfully’ }}

) ;

} else {

this.setState (

{ flash: { status: ‘is-danger’, msg: ‘Please enter name and price’ }}

) ;

}

};

handleChange = e => this.setState ({ [e.target.name]: e.target.value, error: «» }) ;

render () {

const { name, price, stock, shortDesc, description } = this.state;

const { user } = this.props.context;

return! (user && user.accessLevel < 1)? (

): (

<>

 

 

 

 

Add Product

 

 

 

 

 

 

 

 

 

 

 

 

 

 

<input

className="input"

type="text"

name="name"

value={name}

onChange={this.handleChange}

required

/>

 

 

 

 

<input

className="input"

type="number"

name="price"

value={price}

onChange={this.handleChange}

required

/>

 

 

 

 

<input

className="input"

type="number"

name="stock"

value={stock}

onChange={this.handleChange}

/>

 

 

 

 

<input

className="input"

type="text"

name="shortDesc"

value={shortDesc}

onChange={this.handleChange}

/>

 

 

 

 

<textarea

className="textarea"

type="text"

rows="2"

style={{ resize: «none» }}

name="description"

value={description}

onChange={this.handleChange}

/>

 

 

{this.state.flash && (

<div className={`notification ${this.state.flash.status}`}>

{this.state.flash.msg}

 

 

) }

 

 

<button

className="button is-primary is-outlined is-pulled-right"

type="submit"

onClick={this.save}

>

Submit

 

 

 

 

 

 

 

 

</>

) ;

}

}

export default withContext (AddProduct) ;

Этот компонент делает несколько вещей. Он проверяет, хранится ли текущий пользователь в контексте и имеет ли этот пользователь значение accessLevelменьше 1 (то есть является ли он администратором). Если это так, он отображает форму для добавления нового продукта. Если нет, он перенаправляет на главную страницу приложения.

Еще раз, пожалуйста, имейте в виду, что эту проверку можно легко обойти на клиенте. В реальном приложении вы бы выполнили дополнительную проверку на сервере, чтобы убедиться, что пользователю разрешено создавать новые продукты.

Предполагая, что форма отображается, есть несколько полей для заполнения пользователем (из которых nameи priceявляются обязательными). Все, что вводит пользователь, отслеживается в состоянии компонента. Когда форма отправлена, saveвызывается метод компонента, который отправляет Ajax-запрос к нашей серверной части для создания нового продукта. Мы также создаем уникальный идентификатор (который ожидает json-сервер) и также передаем его. Код для этого взят из темы на Stack Overflow.

Наконец, мы вызываем addProductметод, который мы получили через контекст, чтобы добавить вновь созданный продукт в наше глобальное состояние и сбросить форму. Предполагая, что все это прошло успешно, мы устанавливаем flashсвойство в состоянии, которое затем обновит интерфейс, чтобы сообщить пользователю, что продукт был создан.

Если поля nameили priceотсутствуют, мы устанавливаем flashсвойство, чтобы сообщить об этом пользователю.

Добавить страницу продукта

Потратьте секунду, чтобы проверить свой прогресс. Войдите в систему как администратор (электронная почта: admin@example.com, пароль: password) и убедитесь, что вы видите кнопку «Добавить продукт «в навигации. Перейдите на эту страницу, затем используйте форму, чтобы создать пару новых продуктов. Наконец, вернитесь на главную страницу и убедитесь, что новые продукты отображаются в списке продуктов.

Добавление управления корзиной

Теперь, когда мы можем добавлять и отображать товары, осталось реализовать управление корзиной. Мы уже инициализировали нашу корзину как пустой объект в App.js, но нам также нужно убедиться, что мы загружаем существующую корзину из локального хранилища при загрузке компонента.

Обновите componentDidMountметод App.jsследующим образом:

async componentDidMount () {

let user = localStorage.getItem («user») ;

let cart = localStorage.getItem («cart») ;

const products = await axios.get (‘http: //localhost:3001/products’) ;

user = user? JSON.parse (user): null;

cart = cart? JSON.parse (cart): {};

this.setState ({ user, products: products.data, cart }) ;

}

Далее нам нужно определить функции корзины (также в App.js). Сначала мы создадим addToCartметод:

addToCart = cartItem => {

let cart = this.state.cart;

if (cart[cartItem.id]) {

cart[cartItem.id].amount += cartItem.amount;

} else {

cart[cartItem.id] = cartItem;

}

if (cart[cartItem.id].amount > cart[cartItem.id].product.stock) {

cart[cartItem.id].amount = cart[cartItem.id].product.stock;

}

localStorage.setItem («cart», JSON.stringify (cart));

this.setState ({ cart }) ;

};

Этот метод добавляет элемент, используя идентификатор элемента в качестве ключа для объекта корзины. Мы используем объект, а не массив для корзины, чтобы облегчить поиск данных. Этот метод проверяет объект корзины, чтобы увидеть, существует ли элемент с этим ключом. Если это так, это увеличивает сумму; в противном случае создается новая запись. Второй ifоператор гарантирует, что пользователь не сможет добавить больше элементов, чем доступно на самом деле. Затем метод сохраняет корзину в состояние, которое передается другим частям приложения через контекст. Наконец, метод сохраняет обновленную корзину в локальном хранилище для сохранения.

Далее мы определим removeFromCartметод для удаления определенного продукта из пользовательской корзины и clearCartдля удаления всех продуктов из пользовательской корзины:

removeFromCart = cartItemId => {

let cart = this.state.cart;

delete cart[cartItemId];

localStorage.setItem («cart», JSON.stringify (cart));

this.setState ({ cart }) ;

};

clearCart = () => {

let cart = {};

localStorage.removeItem («cart») ;

this.setState ({ cart }) ;

};

Метод removeCartудаляет продукт, используя предоставленный ключ продукта. Затем он соответствующим образом обновляет состояние приложения и локальное хранилище. Метод clearCartсбрасывает состояние корзины до пустого объекта и удаляет запись корзины в локальном хранилище.

Корзина

Теперь мы можем приступить к созданию пользовательского интерфейса корзины. Подобно списку продуктов, мы достигаем этого, используя два элемента: первый, Cart.jsкоторый отображает макет страницы, и список товаров в корзине, используя второй компонент CartItem.js:

//. /src/components/Cart.js

import React from «react»;

import withContext from «.../withContext»;

import CartItem from «. /CartItem»;

const Cart = props => {

const { cart } = props.context;

const cartKeys = Object.keys (cart || {}) ;

return (

<>

 

 

 

 

My Cart

 

 

 

 

 

 

 

{cartKeys.length? (

 

 

{cartKeys.map (key => (

<CartItem

cartKey={key}

key={key}

cartItem={cart[key]}

removeFromCart={props.context.removeFromCart}

/>

))}

 

 

 

 

 

<button

onClick={props.context.clearCart}

className="button is-warning "

>

Clear cart

{» «}

<button

className="button is-success"

onClick={props.context.checkout}

>

Checkout

 

 

 

 

 

 

 

): (

 

 

 

No item in cart!

 

 

 

) }

 

 

</>

) ;

};

export default withContext (Cart) ;

Компонент Cartтакже передает метод из контекста в CartItem. Компонент Cartперебирает массив значений объекта контекстной корзины и возвращает CartItemдля каждого. Он также предоставляет кнопку для очистки пользовательской корзины.

Далее идет CartItemкомпонент, который очень похож на ProductItemкомпонент, но с несколькими тонкими изменениями:

Сначала создадим компонент:

cd src/components

touch CartItem.js

Затем добавьте следующее содержимое:

import React from «react»;

const CartItem = props => {

const { cartItem, cartKey } = props;

const { product, amount } = cartItem;

return (

 

 

 

 

 

 

 

 

 

 

<img

src="https://bulma.io/images/placeholders/128x128.png"

alt={product.shortDesc}

/>

 

 

 

 

 

 

 

{product.name}{» «}

${product.price}

 

 

{product.shortDesc}

 

{`${amount} in cart`}

 

 

<div

className="media-right"

onClick={ () => props.removeFromCart (cartKey) }

>

 

 

 

 

 

 

 

 

) ;

};

export default CartItem;

Этот компонент показывает информацию о продукте и количество выбранных элементов. Также предусмотрена кнопка для удаления товара из корзины.

Наконец, нам нужно добавить метод проверки в Appкомпонент:

checkout = () => {

if (! this.state.user) {

this.routerRef.current.history.push («/login») ;

return;

}

const cart = this.state.cart;

const products = this.state.products.map (p => {

if (cart[p.name]) {

p.stock = p.stock — cart[p.name].amount;

axios.put (

`http: //localhost:3001/products/${p.id}`,

{...p },

)

}

return p;

}) ;

this.setState ({ products }) ;

this.clearCart () ;

};

Этот метод проверяет, вошел ли пользователь в систему, прежде чем продолжить. Если пользователь не вошел в систему, он перенаправляет пользователя на страницу входа, используя ссылку на маршрутизатор, которую мы Routerранее прикрепили к компоненту.

Как правило, на обычном сайте электронной коммерции именно здесь будет происходить процесс выставления счетов, но для нашего приложения мы просто предположим, что пользователь оплатил, и поэтому удалим приобретенные товары из списка доступных товаров. Мы также будем использовать axios для обновления уровня запасов в нашей серверной части.

Таким образом, нам удалось заполнить нашу базовую корзину.

Товары

Вывод

В ходе этого урока мы использовали React для построения интерфейса базовой корзины покупок. Мы использовали контекст для перемещения данных и методов между несколькими компонентами и json-сервером для сохранения данных. Мы также использовали аутентификацию json-server для реализации базового потока аутентификации.

Это приложение ни в коем случае не является готовым продуктом и может быть улучшено во многих отношениях. Например, следующим шагом будет добавление надлежащей серверной части с базой данных и выполнение проверки подлинности на сервере. Вы также можете предоставить пользователям-администраторам возможность редактировать и удалять продукты.

Надеюсь, вам понравился этот урок. Не забывайте, что код этого приложения доступен на GitHub.

3D-печать5GABC-анализAndroidAppleAppStoreAsusCall-центрChatGPTCRMDellDNSDrupalExcelFacebookFMCGGoogleHuaweiInstagramiPhoneLinkedInLinuxMagentoMicrosoftNvidiaOpenCartPlayStationPOS материалPPC-специалистRuTubeSamsungSEO-услугиSMMSnapchatSonyStarlinkTikTokTwitterUbuntuUp-saleViasatVPNWhatsAppWindowsWordPressXiaomiYouTubeZoomАвдеевкаАктивные продажиАкцияАлександровск ЛНРАлмазнаяАлчевскАмвросиевкаАнализ конкурентовАнализ продажАнтимерчандайзингАнтрацитАртемовскАртемовск ЛНРАссортиментная политикаБелгородБелицкоеБелозерскоеБердянскБизнес-идеи (стартапы)БрендБрянкаБукингВахрушевоВендорВидеоВикипедияВирусная рекламаВирусный маркетингВладивостокВнутренние продажиВнутренний маркетингВолгоградВолновахаВоронежГорловкаГорнякГорскоеДебальцевоДебиторкаДебиторская задолженностьДезинтермедитацияДзержинскДивизионная система управленияДизайнДимитровДирект-маркетингДисконтДистрибьюторДистрибьюцияДобропольеДокучаевскДоменДружковкаЕкатеринбургЕнакиевоЖдановкаЗапорожьеЗимогорьеЗолотоеЗоринскЗугрэсИжевскИловайскИрминоКазаньКалининградКировскКировскоеКомсомольскоеКонстантиновкаКонтент-маркетингКонтент-планКопирайтингКраматорскКрасноармейскКрасногоровкаКраснодарКраснодонКраснопартизанскКрасный ЛиманКрасный ЛучКременнаяКураховоКурскЛисичанскЛуганскЛутугиноМакеевкаМариупольМаркетингМаркетинговая информацияМаркетинговые исследованияМаркетинговый каналМаркетинг услугМаркетологМарьинкаМедиаМелекиноМелитопольМенеджментМерчандайзерМерчандайзингМиусинскМолодогвардейскМоскваМоспиноНижний НовгородНиколаевНиколаевкаНишевой маркетингНовоазовскНовогродовкаНоводружескНовосибирскНумерическая дистрибьюцияОдессаОмскОтдел маркетингаПартизанский маркетингПервомайскПеревальскПетровскоеПлата за кликПоисковая оптимизацияПопаснаяПравило ПаретоПривольеПрогнозирование продажПродвижение сайтов в ДонецкеПроизводство видеоПромоПромоушнПрямой маркетингРабота для маркетологаРабота для студентаРазработка приложенийРаспродажаРегиональные продажиРекламаРеклама на асфальтеРемаркетингРетро-бонусРибейтРитейлРовенькиРодинскоеРостов-на-ДонуРубежноеСамараСанкт-ПетербургСаратовСватовоСвердловскСветлодарскСвятогорскСевастопольСеверодонецкСеверскСедовоСейлз промоушнСелидовоСимферопольСинергияСколковоСлавянскСнежноеСоздание сайтов в ДонецкеСоледарСоциальные сетиСочиСтаробельскСтаробешевоСтахановСтимулирование сбытаСуходольскСчастьеТелемаркетингТельмановоТираспольТорговый представительТорезТрейд маркетингТрейд промоушнТюменьУглегорскУгледарУкраинскХабаровскХарцызскХерсонХостингЦелевая аудиторияЦифровой маркетингЧасов ЯрЧелябинскШахтерскЮжно-СахалинскЮнокоммунаровскЯндексЯсиноватая