Nodemon: полезная утилита для разработки, которая позволяет запускать и обновлять сервер автоматически, когда в файлы вносятся изменения.
Логика приложения разделена: выделена папка Controllers, в которой хранятся файлы, отвечающие за манипуляцию с данными, логику обработки данных во всем приложении.
Маршруты (папка routes) содержат адреса конечных точек (endpoints), и обращения к необходимым функциям, реализованных в контроллерах [16]. Для считывания или изменения данных используется экспортированная mongoose Schema той коллекции, с которой необходимо работать. На рисунке12 изображена схема взаимодействия внутри сервера, когда приходит запрос с клиентской части.
Рис. 12. Схема обработки запроса в серверной части приложения
Рассмотрим взаимодействие на примере получения данных обо всех товарах пользователя.
Шаг 1. Отправка данных с клиентской части
Для управления состоянием приложения в клиентской части используется Redux и Redux-thunk. Получение данных - это как раз thunkфункция. В ней выполняется GET-запрос по адресу `/api/items/:id'. Ранее в настройках axiosбыл указан параметр «BaseURL: `http:/localhost:5000'» поэтому можно указать только относительный путь.
Синтаксис ES6 позволяет использовать интерполяцию, и вставлять значение параметра прямо в строку, используя конструкцию ${<Параметр>}.
export const getAllMine = (userId: string) => (
dispatch: Dispatch<AppActionType>
) => {
dispatch(sendLoading());
axios
.get(`/api/items/${userId}`)
.then((res) => dispatch(sendItems(res.data)))
.catch((error) => dispatch(sendErrors(error.response.data)));
};
Шаг 2. Поиск конечной точки по маршруту
В папке «api/routes/» находится файл «items.js», где с помощью объекта router из библиотеки Express.jsможно получить доступ к функции контроллера«.getAllMine()».
const express = require('express');
const router = express.Router();
const items = require('../../controllers/items.controller');
//@route GET api/items/:id
//@desc получаем все товары пользователя
router.get('/:id', items.getAllMine);
Также, для того, чтобы приложение имело доступ к этим маршрутам, необходимо экспортировать их в главный файл «server.js», и вызвать команду «app.use()».
const items = require('./routes/api/items');
const app = express();
app.use('/api/items', items);
Шаг 3. Вызов функции в контроллере
Функция «.getAllMine()» - асинхронная. Реализация функции может быть на основе промисов (как в примере ниже), так и используя ключевые слова «async»,«await». Функция принимает до трех параметров: первый параметр - это объект запроса, второй параметр - объект ответа, который отправится на клиентскую сторону, третий параметр - это функция «.next()», которая может указывать, что выполнение еще не закончено, и требуется вызвать следующую функцию.
//найти товары пользователя
exports.getAllMine = (req, res) => {
const userId = req.params.id;
Items.findOne({ userId })
.then((items) => {
if (!items) {
return res.status(400).send('Ошибка пользователя нет в базе товаров!');
} else {
if (items.items) {
return res.status(200).send(items.items);
} else
return res
.status(400)
.send(
'Пока у вас нет товаров, чтобы обменяться! Добавьте новый товар!'
);
}
})
.catch((err) => {
return res.status(500).send(err);
});
Шаг 4. Возвращение результата запроса
Возвращение результата происходит внутри потребителя «.then()». Сначала для результата устанавливается HTTP-статус, а затем с помощью метода «.send()» отправляются необходимые данные. При этом «.send()» принимает как JSONтак и обычные типы JavaScript.
Подключение к базе данных и настройка удаленного сервера
Необходимо иметь MongoDBна компьютере, на котором ведется разработка. Установочный дистрибутив MongoDB«весит» 263МБ для операционной системы Windows 10.
Сама база данных будет развернута на удаленном сервере с помощью облачного сервиса MongoDBAtlas. В рамках бесплатного тарифа M0 MongoDBAtlasпредоставляет 500 мегабайт для хранения запросов и коллекций документов. Подключиться к удаленной базе данных можно и через локальный клиент MongoDBCompass. Для того, чтобы можно было получить доступ к удаленной базе данных необходимо добавить IPадрес в список разрешенныхIPадресов, создать пользователя и назначить ему соответствующие права доступа. Затем получить строку подключения MongoURI. Эту строку можно использовать в приложении.
База данных подключена к серверной части, через строку подключения (MongoURI) и метод «.connect()», который принимает MongoURI (для удобства MongoURI вынесен в отдельный модуль). Затем можно указать дополнительные параметры.
const mongoose = require('mongoose');
const db = require('./config/keys').mongoURI;
//db подключение
mongoose
.connect(db, {
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: false
})
.then(() => console.log('Подключено к базе!'))
.catch((err) => {
console.log('Подключение невозможно');
console.log(err);
process.exit();
});
Разработка клиентской части сервиса
В этом разделе описано, почему выбран функциональный подход к разработке компонентов React, перечислены реализованные компоненты, дано описание процесса управления состоянием клиентского приложения с помощью технологии Redux и механизм отправки данных на сервер и получения данных. Также в конце раздела представлено описание реализованных визуальных форм.
Исходный код клиентской части приложения для свободного обмена одеждой опубликован в виде репозитория на GitHub. Доступ к исходному коду по ссылке: https://github.com/plastya-flomaster/swop-frontend.
Функциональный подход к созданию компонентов React
Функциональные компоненты в Reactдают возможность использовать новую технологию ReactHooks.Hooks -- нововведение в React 16.8, которое позволяет использовать состояние и другие возможности React без написания классов.
В React нет способа «присоединить» повторно используемое поведение к компоненту (например, подключение к хранилищу). Типичное крупное React-приложение представляет собой глубокое дерево иерархии компонентов, покружённых провайдерами, консьюмерами, компонентами высшего порядка, рендер-пропсами и другими абстракциями. Это проблема React, поэтому с 16.8 версии был предложен более удобный способ повторно использовать логику вокруг состояния.
С помощью Hooks можно извлечь логику состояния из компонента, чтобы её протестировать или повторно использовать. Hooks позволяют повторно использовать логику состояния, не затрагивая дерево компонентов. Благодаря этому, Hooks легко использовать в разных компонентах и делиться ими с сообществом.
В официальной документации Reactприводится два основных правила для использования Hooks:
Hooks следует вызывать только на верхнем уровне. Недопустим вызов hooks внутри циклов, условий или вложенных функций.
Hooks следует вызывать только из функциональных компонентов React. Недопустим вызов hooks из обычных JavaScript-функций. (Не распространяется на пользовательские hooks).
Более того, функциональный подход в написании компонентов позволяет облегчить восприятие и написание кода, исчезает парадигма жизненного цикла компонента, которая была неотъемлемой частью классовых компонентов. Hooks описывают все так, как оно должно быть в любой момент времени, и помогают не думать о том, как компоненту реагировать на изменения. В будущем чистые функции будут выигрывать по скорости работы в сравнении с классами из-за отсутствия методов жизненного цикла. Функции легче: для создания функции не нужно создавать класс, наследоваться от интерфейса (ключевое слово «extends»), не нужно создавать конструктор. А использование чистых функций уменьшает вероятность написания плохого кода. У разработчика нет необходимости использовать контекст. Код избавляется от конструкции thisи становится более удобочитаемым. С помощью чистых функций создаются компоненты без внутреннего состояния. Описание компонентов с помощью чистых функций создает меньше кода, следовательно, код легче поддерживать. Чистые функции намного проще тестировать. Необходимо просто передать propsи ожидать определенных действий.
Реализованные компоненты сервиса
В исходном коде проекта компоненты хранятся в папках, которые объединяют компоненты по смыслу. Самые объемные компоненты - компоненты, отвечающие за рендеринг целых страниц. Они находятся в папке «Pages». Таких компонентов всего шесть:
Компонент «Главная страница».
Компонент «Страница помощь».
Компонент «Страница редактирования данных о пользователе».
Компонент «Сведения о пользователе».
Компонент «Страница совпадений».
Вспомогательный компонент «Страница 404». Рендерится тогда, когда пользователь перешел на некорректный URL.
Для входа в приложение реализовано два компонента: «Окно регистрации пользователя» и «Окно входа в приложение».
Отображение ленты товаров (карточки товаров) реализовано вместе с вспомогательными кнопками «свайп влево» и «свайп вправо». Это компоненты группы «Cards».
Большое количество компонентов создано, для реализации функционала товаров: компонент для добавления и отображения информации о товаре, отображение всех товаров пользователя, отображения одного товара в сокращенном виде.
Также для сервиса реализовано два Reactrouter'а. Роутеры позволяют рендерить определённый компонент во время обращения к нему (например, при изменении URLили переходе по ссылке). Технологии динамического роутинга позволяют не держать в памяти приложения все варианты, а отображать только то, с чем работает пользователь, а остальным компонентам присваивать значение «null».
В приложении есть и более мелкие компоненты, которые не содержат логики или она достаточно простая, например: компонент «UserPic» служит, чтобы отобразить имя и фотографию пользователя, компонент «TagsInput» - самостоятельно реализованное поле ввода тегов и др.
Управление состоянием. React + Redux
Redux- это контейнер предсказуемых состояний для JavaScript приложений. Redux возник из идеи Flux -архитектурного паттерна, созданного инженерами из Facebook.Redux, с точки зрения кодаJavaScript- это объект, внутри которого лежат данные. Он используется остальными частями приложения для их хранения, изменения и извлечения. В терминологии Redux он называется контейнер, так как данные хранятся внутри.
Официальная документация Reduxрекомендует использовать свою библиотеку в тех случаях, если:
Существуют обоснованные объемы данных, меняющихся со временем.
Необходим один источник информации для состояния приложения.
Хранение состояния приложения в компоненте верхнего уровня уже недостаточно или небезопасно.
Основными терминами Redux являются:
State - состояние приложения (или его части) хранящееся в Store. Также есть специальный initialState - объект, описывающий начальное состояние приложения.
Store - все состояние приложения сохранено внутри этого объекта. Единственный способ изменить дерево состояния - это вызвать action.
Action - объект, который описывает то, что должно произойти. ПО соглашению, каждый Actionимеет тип (type) и загрузку (payload). Тип классически задается строкой, а загрузка это та часть нового состояния, которое предстоит изменить в Reducer.
Reducer - функция, которая ожидает Actionи в соответствии с тем, какой Actionпришел -возвращает новое состояние. Важно отметить то, что состояние никогда не мутирует, а всегда изменяется и замещается. Это позволяет Redux стабильно управлять состоянием всего приложения.
На рисунке 13 продемонстрирована схема взаимодействия при использовании технологии Redux. Как видно из схемы, действия могут поступать с представления от пользователя, или приходить из некого внешнего API. А представление ожидает изменения состояния в Store.
Рис. 13. Поток данных Redux + React.
Первоначальная настройка Reduxтребует относительно большого количества кода и времени, однако эта настройка выполняется один раз и для других компонентов может быть сделана по аналогии.
В проект была загружена библиотека Reduxс помощью пакетного менеджера NPM(Команда для загрузки«npminstall --saveredux»). Поскольку разработка ведется на языке Typescript, необходимо было также установить «@types»для библиотеки Redux. Затем аналогичной командой нужно установить библиотеку-связку с React «react-redux» и «redux-devtools»для того, чтобы стала возможна отладка в браузере.
Дальнейшая настройка Reduxбудет описана по шагам. Рассмотрим процесс настройки управления состояниями товаров.
Шаг 1. Инициализация Store и подключение Dev-tools
Для того, чтобы избежать путаницы и структурировать исходный код проекта, была создана новая директории «Redux», где выделено еще три директории: «Actions», «Stores», «Reducers». Каждая директория, соответственно хранит файлы, относящиеся к этим сущностям Redux. Все файлы в этих папках будут иметь расширение «.ts», поэтому потребуется дополнительный код, отвечающий за типизацию создаваемых объектов.
В папке Storesсоздан файл «Store.ts». Ниже представлен полный код, описывающий хранилище состояний в Redux.
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../Reducers/rootReducer';
const initialState = {};
export type AppState = ReturnType<typeof rootReducer>
const store = createStore(
rootReducer,