Рубрики
Без рубрики

Приложение фотогалерея с Express, React, Chakra Ui и Multer

Создайте полную фотогалерею Photo Gallery с Express и MULTER на бэкэнд и реагировать и чакра-интерфейс на интерфейсе.

Автор оригинала: Foysal.

Первоначально написано и опубликовано на Официальный блог Logocket’s Blog Отказ Cover Photo Credit идет к Lognocket.

React.js теперь в настоящее время является одним из самых тестируемых с битвами и созреваемыми фрейтами Frontend в мире и Express.js – это аналог среди структуры Backend/Server. Если вы создаете приложение сегодня, вы не можете выбрать лучшего дуэта, чем это. В этом посте я выберу тебя через создание полного приложения, используя эти две рамки.

  1. Это сообщение для меня?
  2. Укладка земли
  3. Фотогалерея с чакрауэй и реагировать
  4. Server API для хранения фото и информации в базе данных с Express и Sequelize
  5. Подключите галерею с API Server
  6. UX Tidbits
  7. Закрытие замечаний

Перед тем, как мы погрузимся, за нетерпеливые жители, как я, вот вся кодовая база на Github Отказ Не стесняйтесь клонировать его и возьмите его для спина.

Как самоуччели, я всегда нахожусь в Интернете в Интернете в блоге/учебных постах, которые создают все приложение с нуля, демонстрирующих один или несколько широко виден функций в реальных приложениях. Этот вид постов помогает разработанию по большому спектру квалификации/опыта. Новички Узнайте, как приклеить новые концепции, которые они недавно узнали и превратили его в полное и пригодное приложение. Devs С промежуточным уровнем знаний может научиться организовать, структурировать и применять лучшие практики при создании полных приложений. Итак, если вы просто попадаете в экосистему JS или, если вы уже построили одну или две собственные приложения, используя JS, но иногда запутаются, если вы делаете это правильно, этот пост – для тебя Отказ

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

  • Очистить понимание основных концепций JS и некоторого знакомства с синтаксисом ES6
  • Используются как минимум один раз, знакомство о том, что это общие концепции, такие как состояния, компоненты, рендеры и т. Д.
  • Знакомство с концепцией API отдыха
  • использованная реляционная база данных
  • Используется Node.js и Express.js для приложения веб-сервера
  • Рабочая настройка экосистемы JS на вашей машине. I.E: NPM, NODEJS и т. Д. Последние версии установлены

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

Пожалуйста, обратите внимание, что : Моя основная ОС Ubuntu, поэтому все команды в этом посте предполагают, что у вас есть система * NIX.

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

Наше приложение будет иметь 2 основных запчасти. Одним из них является приложение для оператора клиента, которое позволяет загружать фотографии через мой браузер. Загруженные фотографии затем отображаются в виде галереи. Другой – это Server Side API, который получает фото загрузку и сохраняет его где-то и позволяет нам запросить и отображать все загруженные фотографии.

Однако до того, как все это программирование Mumbo-Jumbo, давайте давайте приложим наше приложение заваченное имя. Я называю это Фотографий но не стесняйтесь дать ему лучшее имя самого себя и дайте мне знать, что вы придумаете

Хорошо, время для кода. Давайте сначала сделаем папки контейнеров для нашего приложения:

mkdir photato && cd $_
mkdir web
mkdir api

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

cd web
npx create-react-app web

Теперь вы должны иметь кучу файлов и папок в Веб/ Папка и выходные данные скажут вам, что, идя в каталог и запустите Пряжа начать Вы можете сделать ваше приложение доступным в http://localhost: 3000 Отказ

Создать файлы сгенерированные приложения React

Теперь, если вы построили веб-сайты/WebApps раньше, вы можете быть знакомы с борьбой здания UIS с Raw HTML и CSS и знают, что инструментарии/библиотеки UI, такие как Twitter Bootstrap, семантический интерфейс, материальный комплект и т. Д. Потреблять devs, который не может производить «Дрибблбл известный» качество дизайна. В этом посте мы рассмотрим у самых распространенных и традиционных библиотек интернет-пользовательских интернет-пользователей, упомянутых выше и использования Чакра-пользователь , построенный на утилите Frame CSS Framework Cipwind CSS и с доступностью в виду.

Следуя руководству Chakra UI Начните GATION, запустите следующие команды в корневом каталоге React App:

yarn add @chakra-ui/core @emotion/core @emotion/styled emotion-theming

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

Последнее, что нам нужно, прежде чем мы сможем начать кодирование, это добавить еще одну библиотеку, чтобы получить Довольно выглядящая галерея :

yarn add react-photo-gallery

Код нашего приложения будет инкапсулирован в пределах SRC/ Каталог, так что давайте посмотрим на это. Create-raction-app дал нам кучу файлов и с помощью Chakra Ui, мы можем в основном избавиться от всех CSS. Удалить App.csss , index.csss и logo.svg файл:

cd src
rm -r App.css index.css logo.svg

Это дает нам чистую базу для начала здания. Теперь давайте посмотрим на нашу настройку для приложения API Server. Перейдите к папке API и создайте новые файлы, выполнив следующие команды:

cd ../../api
touch package.json

Теперь скопируйте-вставьте следующий код в Package.json файл:

{
  "name": "api",
  "version": "1.0.0",
  "description": "Server api for photato",
  "main": "dist",
  "author": "Foysal Ahamed",
  "license": "ISC",
  "entry": "src/index.js",
  "scripts": {
    "dev": "NODE_ENV=development nodemon src/index.js --exec babel-node",
    "start": "node dist",
    "build": "./node_modules/.bin/babel src --out-dir dist --copy-files",
    "prestart": "npm run -s build"
  },
  "eslintConfig": {
    "extends": "eslint:recommended",
    "parserOptions": {
      "ecmaVersion": 7,
      "sourceType": "module"
    },
    "env": {
      "node": true
    },
    "rules": {
      "no-console": 0,
      "no-unused-vars": 1
    }
  },
  "dependencies": {
    "cors": "^2.8.4",
    "express": "^4.13.3",
    "mysql2": "^1.6.1",
    "sequelize": "^5.18.4"
  },
  "devDependencies": {
    "@babel/cli": "^7.1.2",
    "@babel/core": "^7.1.2",
    "@babel/node": "^7.0.0",
    "@babel/plugin-proposal-class-properties": "^7.1.0",
    "@babel/preset-env": "^7.1.0",
    "eslint": "^3.1.1",
    "eslint-config-airbnb": "^17.1.0",
    "eslint-plugin-jsx-a11y": "^6.2.1",
    "nodemon": "^1.9.2"
  }
}

Обратите внимание, что у нас есть довольно много Зависимости Dev И им необходимы для включения записи нашего приложения в использовании последних синтаксиса ES6, транспортированным через Babel. Babel – великолепный инструмент и полный замечательных особенностей, но использовать его, вам нужно знать почти ничего об этом. В нашем случае нам просто нужно создать .babelrc Файл рядом с Package.json Файл и поставьте следующий конфигурацию в нем:

{
    "presets": [[
        "@babel/preset-env",
        {
            "targets": {
                "node": "current"
            }
        }
    ]],
    "plugins": [
        "@babel/plugin-proposal-class-properties"
    ]
}

Есть также несколько зависимостей, таких как Экспресс и Sequelize ] И мы увидим их использование позже. Это все настройки нам нужны для нашего сервера приложения и, но прежде чем мы будем двигаться дальше, давайте установим все пакеты, работающие NPM установить Команда в корне папки API. Эта команда будет генерировать Node_Modules/ Папка и Package.lock.json файл.

Начнем с App.js файл. Давайте очистим сгенерированный код и заполните его следующим кодом:

import React from 'react';
import { ThemeProvider } from '@chakra-ui/core';

import AppContainer from './app.container';

function App() {
    return (
        
); } export default App;

Это упрощает наш входной компонент и делегирует фактическую логику/поведение в другой контейнер с именем AppContainer который обернут внутри Темапровидер от чакрауэй. Темапровидер Компонент гарантирует, что все это дети могут быть введены в тему Chakra UI или какой-либо пользовательской темы, которую вы можете захотеть перейти к нему.

Сейчас с таким образом, мы никогда не придется трогать App.js очередной раз. Давайте создадим новый файл touch src/app.container.js и заполните его следующим кодом:

import React from 'react';
import PhotoGallery from 'react-photo-gallery';

import Header from './header.component';

function AppContainer() {
    const photos = [{
            src: 'http://placekitten.com/200/300',
            width: 3,
            height: 4,
        },
        {
            src: 'http://placekitten.com/200/200',
            width: 1,
            height: 1,
        },
        {
            src: 'http://placekitten.com/300/400',
            width: 3,
            height: 4,
        },
    ];

    return (
        <>
            
); } export default App;

Этот компонент оказывает 2 других компонента, Заголовок и Фотогалерея где Фотогалерея Предоставляется Photo Gallery NPM Lib. Обратите внимание, что мы пропускаем массив фотографий, содержащих изображения заполнения на Фотогалерея Компонент, мы вернемся к нему позже на посту и замените сердечные фотографии котенка с нашими собственными загруженными фотографиями

Другой компонент Заголовок импортируется из файла, который еще не существует, так что давайте создадим его: Нажмите SRC/Header.comPonent.js и поместите следующий код в файл:

import React from 'react';
import { Flex, Button, Text } from '@chakra-ui/core';

function Header ({
    isUploading = false, 
    onPhotoSelect,
}) {
    return (
        
            
                
                    🥔 
                 
                
                    🍠 
                 
                Photato
            

            
                
            
        
    );
};

export default Header;

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

Первый взгляд с котятами

Теперь давайте сломаемся тем, что мы сделали до сих пор.

Компонент заголовка оборачивает все это дети в чакре UI Flex Компонент, который отображает HTML Div Элемент с стилем CSS Дисплей: Flex Отказ Будучи коммунальной средой CSS CSS, Chakra UI позволяет вам пройти различные реквизиты к компонариям, чтобы стилить его по своему вкусу, и вы увидите это по всему нашему приложению. В нашей обертке Flex Компонент PX и PY реквизиты дают ему хорошую горизонтальную и вертикальную прокладку (соответственно) и оправдать = "пространство между" ОПРУЖКУ гарантирует, что элементы внутри него отображаются с равным расстоянием между ними. Если вы не знакомы с CSS Flexbox, я настоятельно рекомендую вам узнать больше об этом удивительном инструменте макета.

Внутри Flex Контейнер, у нас есть Текст Слева от экрана и Кнопка Для загрузки новых фотографий справа от экрана. Давайте посмотрим на Кнопка здесь. Мы используем Размер = "см" Чтобы дать ему меньший размер, но вы можете играть с LG , XS и т.д. Значения для изменения размера. Вариант = "Наброски" опоры делает его границем, а не заполнять ее цвет и говорить о цвете, VariantColor = "Blue" делает границу и текст синим. Есть несколько других цветов, доступных из коробки из Chakra Ui, и я очень рекомендую чтение на этом Отказ

До сих пор мы были сосредоточены на внешности. Давайте поговорим о функциональности. Этот компонент представляет собой отличный пример одного из основных принципов написания чистого и легко поддерживаемого кода Frontend. Это Тупой компонент Это только оказывает разметку и не обрабатывается логики. Чтобы сделать его функционалом, мы проходим реквизит к этому от родителя. Он ожидает, что два реквизита:

  • Isuploading который является логией и по умолчанию для ложь Отказ Это опоры определяет состояние Загрузить кнопку Отказ Когда это правда, кнопка поедет в Загрузка Состояние дать пользователю отзыв о том, что загрузка происходит на заднем плане.
  • OnPhotoselect Какая функция, которая будет срабатываться, когда пользователь выбирает новое фото для загрузки. Мы обратимся к этому позже.

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

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

Точка входа для нашего сервера API будет SRC/index.js Файл, так что давайте создадим это:

mkdir src
touch index.js

Затем поместите следующий код в этот файл:

import http from 'http';
import cors from 'cors';
import express from 'express';
import { Sequelize } from 'sequelize';

const config = {
    port: 3001,
    database: {
        username: "root",
        password: "admin",
        host: "localhost",
        port: "3306",
        dialect: "mysql",
        database: "photato",
    }
};

let app = express();
app.server = http.createServer(app);

// 3rd party middlewares
app.use(cors({}));

// connect to db
const database = new Sequelize(config.database);

database.sync().then(() => {
    app.get('/', (req, res) => {
        res.json({app: 'photato'});
    });

    app.server.listen(config.port, () => {
        console.log(`Started on port ${app.server.address().port}`);
    });
});

export default app;

Это настройка Barebone и давайте сломаем его блок по блоку.

import http from 'http';
import cors from 'cors';
import express from 'express';
import { Sequelize } from 'sequelize';

Импортирует необходимые модули из узла встроенного узла пакета HTTP и другие пакеты 3-го вечеринок, установленные через NPM.

const config = {
    port: 3001,
    database: {
        username: "root",
        password: "admin",
        host: "localhost",
        port: "3306",
        dialect: "mysql",
        database: "photato",
    }
};

Определяет конфигурации для порта базы данных и сервера, где будет доступно приложение. Вам нужно будет изменить базу данных пароль и Имя пользователя На основании вашей настройки базы данных MySQL. Кроме того, убедитесь, что вы создаете новую схему базы данных с именем Фотографий в вашей БД.

Обратите внимание, что: В производстве готовых приложений вы пройдете конфиги из ENV VAR вместо того, чтобы укоренить их.

let app = express();
app.server = http.createServer(app);

// 3rd party middlewares
app.use(cors({}));

Инициализирует приложение Express и создает экземпляр сервера, используя узел http.createserver метод. Express позволяет подключать различные функциональные возможности через Hedimwares. Одно такое промежуточное программное обеспечение, которое мы собираемся использовать, включает запросы CORS для нашего API. Прямо сейчас мы разрешаем CORS запросы от любого происхождения, но вы можете добавить более мелкозернистое конфигурацию, чтобы разрешить запросы только из доменного имени приложения Frontend для целей безопасности.

// connect to db
const database = new Sequelize(config.database);

database.sync().then(() => {
    app.get('/', (req, res) => {
        res.json({app: 'photato'});
    });

    app.server.listen(config.port, () => {
        console.log(`Started on port ${app.server.address().port}`);
    });
});

Инициализирует экземпляр Sequelize, который подключается к нашей базе данных MySQL на основе нашего конфигурации. Как только соединение установлено, он добавляет обработчик для / Конечная точка нашего API, которая возвращает отклик в формате JSON. Затем открывается через порт сервера, указанного в конфиге.

Теперь мы можем загрузить наше приложение и посмотреть, что мы достигли до сих пор. Беги NPM запустить dev В API/ папка, а затем перейти к http://localhost: 3001 И вы должны увидеть что-то вроде этого:

API первый взгляд

Загрузка по обработке файлов имеет много краевых чехлов и проблем безопасности, поэтому это не очень хорошая идея, чтобы построить ее с нуля. Мы будем использовать пакет NPM под названием Малтер Это делает его супер легко. Установите пакет, запустив NPM I --save Multer а затем сделайте следующие изменения в SRC/index.js файл.

import http from 'http';
import cors from 'cors';
import multer from 'multer';
import { resolve } from 'path';

//previously written code here

const config = {
    port: 3001,
    uploadDir: `${resolve(__dirname, '..')}/uploads/`,
    database: {
        username: "root",
        password: "admin",
        host: "localhost",
        port: "3306",
        dialect: "mysql",
        database: "photato",
    }
};

//previously written code here

// connect to db
const database = new Sequelize(config.database);

// setup multer
const uploadMiddleware = multer({ 
    dest: config.uploadDir,
    fileFilter: function (req, file, cb) {
        if (!file.originalname.match(/\.(jpg|jpeg|png|gif)$/)) {
            return cb(new Error('Only image files are allowed!'));
        }
        cb(null, true);
    }, 
}).single('photo');

//previously written code here

    app.get('/', (req, res) => {
        res.json({app: 'photato'});
    });

    app.post('/photo', uploadMiddleware, async (req, res) => {
        try {
            const photo = await Photo.create(req.file);
            res.json({success: true, photo});
        } catch (err) {
            res.status(422).json({success: false, message: err.message});
        }
    });

//previously written code here

Обзор дополнений:

  • Импортирует пакет Multer
  • Добавляет каталог назначения, где будут храниться загруженные файлы. Прямо сейчас он должен быть API/Загрузка/ что не существует, так что давайте также создадим папку: MKDIR загрузка
  • Инициализирует промежуточное программное обеспечение Multer, которая принимает один файл с ключом Фото и сохраняет файл в указанной папке
  • Только позволяет загружать файлы изображений через Multer
  • Добавляет новую конечную точку запроса Post, которая использует промежуточное программное обеспечение загрузки. После того, как файл обрабатывается промежуточным программным обеспечением, он присоединяет информацию о файле, такой как пункт назначения, размер, Mimetype и т. Д. Информация в объект Express Req, который передается следующему обработчику. В этом случае следующий обработчик пытается Чтобы сохранить данные файла в базе данных (мы скоро обсудим это) и на успех, он возвращает ответ JSON, включая данные файлов, а также сбой, он возвращает ответ JSON с сообщением об ошибке.

Эта линия const photo.create (req.file); Однако требуется немного больше объяснений. ModelName.create (Modeldata) Как вы создаете новую строку в таблице базы данных с помощью Sequelize и в приведенном выше коде, мы ожидаем, что модель Sequelize с именем Фото существовать, что мы еще не создали. Давайте исправить это, беги Нажмите SRC/Photo.model.js и поместить следующий код в этот файл:

import { Model, DataTypes } from 'sequelize';

const PhotoSchema = {
    originalname: {
        type: DataTypes.STRING,
        allowNull: false,
    },
    mimetype: {
        type: DataTypes.STRING,
        allowNull: false,
    },
    size: {
        type: DataTypes.INTEGER,
        allowNull: false,
    },
    filename: {
        type: DataTypes.STRING,
        allowNull: false,
    },
    path: {
        type: DataTypes.STRING,
        allowNull: false,
    },
};

class PhotoModel extends Model {
    static init (sequelize) {
        return super.init(PhotoSchema, { sequelize });
    }
};

export default PhotoModel;

Это много кода, но и то, что мы создаем класс Sequelize Model с определением схемы, где поля (столбцы таблицы) являются всеми строками (переводятся на varchar в mysql), за исключением поля для размера, которое является целым числом. Схема выглядит так, потому что после обработки загруженных файлов MULTER предоставляет именно эти данные и прикрепляет его к req.file Отказ

Возвращаясь к тому, как эта модель может использоваться в нашем обработчике маршрута, нам нужно подключить модель MySQL через Sequelize. В SRC/index.js Файл Добавить следующие строки:

// previously written code
import { Sequelize } from 'sequelize';
import PhotoModel from './photo.model';

// previously written code

// connect to db
const database = new Sequelize(config.database);

// initialize models
const Photo = PhotoModel.init(database);

// previously written code

Так что теперь, когда мы собрали недостающий случай Фото Давайте добавим еще одну конечную точку на нашу API и посмотреть еще одно использование модели:

// previously written code

    app.get('/', (req, res) => {
        res.json({app: 'photato'});
    });

    app.get('/photo', async (req, res) => {
        const photos = await Photo.findAndCountAll();
        res.json({success: true, photos});
    });

// previously written code

Это добавляет обработчик получения запроса на /фото Путь и возвращает ответ JSON, содержащий все ранее загруженные фотографии. Обратите внимание, что Фото.findandcountall () Возвращает объект, который выглядит так:

{
    count: ,
    rows: [
        {},
        {},
        ....
    ]
}

Со всеми вышеуказанными изменениями, ваш SRC/index.js Файл должен выглядеть так:

import http from 'http';
import cors from 'cors';
import multer from 'multer';
import express from 'express';
import { resolve } from 'path';
import { Sequelize } from 'sequelize';

import PhotoModel from './photo.model';

const config = {
    port: 3001,
    uploadDir: `${resolve(__dirname, '..')}/uploads/`,
    database: {
        username: "root",
        password: "admin",
        host: "localhost",
        port: "3306",
        dialect: "mysql",
        database: "photato",
    }
};

let app = express();
app.server = http.createServer(app);

// 3rd party middlewares
app.use(cors({}));

// connect to db
const database = new Sequelize(config.database);

// initialize models
const Photo = PhotoModel.init(database);

// setup multer
const uploadMiddleware = multer({ 
    dest: config.uploadDir,
    fileFilter: function (req, file, cb) {
        if (!file.originalname.match(/\.(jpg|jpeg|png|gif)$/)) {
            return cb(new Error('Only image files are allowed!'));
        }
        cb(null, true);
    },
}).single('photo');

database.sync().then(() => {
    app.get('/', (req, res) => {
        res.json({app: 'photato'});
    });

    app.get('/photo', async (req, res) => {
        const photos = await Photo.findAndCountAll();
        res.json({success: true, photos});
    });

    app.post('/photo', uploadMiddleware, async (req, res) => {
        try {
            const photo = await Photo.create(req.file);
            res.json({success: true, photo});
        } catch (err) {
            res.status(400).json({success: false, message: err.message});
        }
    });

    app.server.listen(process.env.PORT || config.port, () => {
        console.log(`Started on port ${app.server.address().port}`);
    });
});

export default app;

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

На данный момент у нас есть 2 приложения. Один – это приложение на основе браузера, которое работает на http://localhost: 3000 а другой – это приложение Server Side.js работает на http://localhost: 3001 Но до сих пор они были незнакомыми друг на друга, живущие на жизнь. Таким образом, естественно, следующий шаг – выйти за выйти за двое и надеюсь, что они живут счастливо.

Мы собираемся использовать браузер Fetch API Чтобы поговорить с нашим сервером приложения из приложения RACT. Чтобы наш серверную связь инкапсулирован, мы создадим новый файл:

cd ../web/
touch src/api.js

Затем давайте добавим следующие функции в этом файле:

const API_URL = 'http://localhost:3001';

export async function getPhotos () {
    const response = await fetch(`${API_URL}/photo`);
    return response.json();
};

export async function uploadPhoto (file) {
    if (!file)
        return null; 

    const photoFormData = new FormData();

    photoFormData.append("photo", file);
    
    const response = await fetch(`${API_URL}/photo`, {
        method: 'POST',
        body: photoFormData,
    });

    return response.json();
};

Давайте сломаемся:

  • У нас есть переменная API_URL Это указывает на URL, где наш серверное приложение доступно.
  • GetPhotos делает запрос на /фото Конечная точка нашего сервера и анализирует ответ как JSON, прежде чем вернуть его.
  • Upplyphoto получает Файл Параметр и сборки Formdata объект, который можно использовать для публикации файла к /фото Конечная точка нашего сервера. После отправки запроса он анализирует ответ как JSON и возвращает его.

Давайте использовать эти нефте-маленькие функции, пожалуйста, мы? Откройте src/app.container.js Файл и добавьте следующие новые строки в нем:

import React, { useState } from 'react';
// previously written code...

import { uploadPhoto } from './api';

function AppContainer() {
    const [isUploading, setIsUploading] = useState(false);
    
    async function handlePhotoSelect (file) {
        setIsUploading(true);
        await uploadPhoto(file);
        setIsUploading(false);
    };

    return (
            // previously written code...
            
// previously written code... ); }

С вышеуказанными изменениями мы добавили государственные крючки в нашем Приложение составная часть. Если вы не знакомы с крючками и государствами, я призываю вас к Читайте на нем Но вкратце, штат позволяет вам повторно представить свой интерфейс UI всякий раз, когда ваша государственная стоимость меняется.

Всякий раз, когда наша функция РулевыеФотоселики выполняется с аргументом файла, он сначала изменится Isuploading значение для правда Отказ Затем он пройдет данные файла нашим Upplyphoto функция и когда это заканчивается, она будет переключаться Isuploading значение для ложь ;

Тогда мы проходим наш Isuploading Состояние как опоры нашего компонента заголовка и если вы вспомните, когда Isuploading изменения в правда наше Загрузить фото Кнопка будет переходить в состояние загрузки. Второй опора OnPhotoselect Получает функцию РулевыеФотоселики Отказ Помните, когда написал наш Заголовок Компонент, который мы определили OnPhotoselect опоры, но никогда не использовали это? Ну давайте урегулируемся, сделав следующие изменения в SRC/header.component.js файл:

// previously written code...
function Header ({
    isUploading = false, 
    onPhotoSelect,
}) {
    let hiddenInput = null;

    // previously written code...

    return (
        // previously written code...
                

                 hiddenInput = el}
                    onChange={(e) => onPhotoSelect(e.target.files[0])}
                />
        // previously written code...
    );
};

Вышеуказанные изменения добавляют скрытый элемент ввода файла и хранит его ссылку на Hiddeninput Переменная. Всякий раз, когда Кнопка Нажат, мы запускаем щелчок на элементе ввода файла, используя ссылочную переменную. Оттуда, встроенный поведение браузера пинает и просит пользователя выбрать файл. После того, как пользователь сделает выбор, Onchange Событие уволено и когда это произойдет, мы называем OnPhotoselect Функция PROP и передайте выбранный файл в качестве аргумента.

Это завершает один канал связи между нашими приложениями Frontend и Backend. Теперь вы должны быть в состоянии выполнить следующие шаги и получить аналогичный результат по пути:

  1. Перейти к http://localhost: 3000
  2. Откройте инструмент разработчика и перейдите на вкладку сети
  3. Нажмите на Загрузить фото Кнопка и выберите файл изображения из локальных папок.
  4. Смотрите новый запрос на почту, отправляемый на http://localhost: 3001//Фотографии и ответ JSON возвращаются.

Вот как мой выглядит:

Первая попытка загрузки файла

Чтобы убедиться, что загрузка работала, перейдите в API/Загрузка каталог, и вы должны увидеть там файл. Попробуйте загрузить больше фотографий и посмотреть, продолжают ли они появляться в этой папке.

Это здорово, верно? Мы на самом деле загружаем наши фотографии через наше приложение raction и сохранение его приложением Node.js Server. К сожалению, последний шаг, чтобы связать все вместе, это заменить эти котенки на наши загруженные фотографии. Для этого нам нужно иметь возможность запросить сервер для загрузки фотографии и получить файл фото. Давайте сделаем это, добавив еще одну конечную точку в API/SRC/index.js файл:

// previously written code...
    app.get('/', (req, res) => {
        res.json({app: 'photato'});
    });

    app.get("/photo/:filename", (req, res) => {
        res.sendFile(join(config.uploadDir, `/${req.params.filename}`));
    });
// previously written code...

Новая конечная точка позволяет нам пройти любую строку вместо : filename Через URL и сервер ищет файл с таким именем в нашем Uploaddir и отправляет файл в ответ. Итак, если у нас есть файл с именем Image1 мы можем получить доступ к этому файлу, перейдя на http://localhost: 3001/Фото/Изображение1 и собираюсь в http://localhost: 3001/Фото/Изображение2 даст нам файл с именем Image2 Отказ

Это было легко, верно? Теперь вернемся к интерфейсу. Помните, как наша исходная котельная Фотографии Переменная выглядела как? Данные, которые мы получаем от сервера, это ничего такого, верно? Мы исправм это первым. Вернитесь к Web/SRC/API.JS файл и внесите следующие изменения:

export async function getPhotos () {
    const response = await fetch(`${API_URL}/photo`);
    const photoData = await response.json();

    if (!photoData.success || photoData.photos.count < 1)
        return [];

    return photoData.photos.rows.map(photo => ({
        src: `${API_URL}/photo/${photo.filename}`,
        width: 1, 
        height: 1,
    }));
};

Дополнительные линии просто преобразуют наш сервер отправленные данные в формат, который можно передавать нашему Фотогалерея составная часть. Это строит SRC URL из API_URL И свойство имени файла каждой фотографии.

Вернуться в app.container.js Файл, мы добавляем следующие изменения:

import React, { useState, useEffect } from 'react';
// previously written code...

import { uploadPhoto, getPhotos } from './api';

function AppContainer() {
    const [isUploading, setIsUploading] = useState(false);
    const [photos, setPhotos] = useState([]);

    useEffect(() => {
        if (!isUploading)
            getPhotos().then(setPhotos);
    }, [isUploading]);
    
    // previously written code...
}

Это оно! Это все, что вам нужно показать загруженные фотографии в галерее изображений. Мы заменили наш статический Фотографии Переменная с переменной состояния и изначально устанавливает его в пустой массив. Самая известная вещь в вышеуказанном изменении – Useffect функция. Каждый раз Isuploading Состояние изменяется, он как побочный эффект, реагирование будет запустить первую функцию аргумента в Useffect вызов. В этой функции мы проверяем, если Isuploading это ложь , что означает, что новая загрузка либо полная, либо компонент загружается в первый раз и только для тех случаев, которые мы выполняем GetPhotos И результаты этой функции хранятся в Фотографии Переменная состояния. Это гарантирует, что помимо загрузки все предыдущие фотографии на первой нагрузке галерея также обновляется с недавно загруженной фотографией, как только загрузка будет завершена без необходимости обновлять окно.

Это весело, поэтому я загрузил 4 последовательных фотографии, и вот как сейчас выглядит моя картошка:

Результат мульти-загрузки

Хотя у нас есть функционирующее приложение, которое соответствует всем требованиям, которые мы изложили, чтобы построить, он может использовать некоторые улучшения UX. Например, успех/ошибка загрузки не запускает никакой обратной связи для пользователя. Мы реализуем это, используя нефте-маленький Тост Компонент из чакры UI. Давайте вернемся к web/src/app.container.js :

// previously written code...
import PhotoGallery from 'react-photo-gallery';
import { useToast } from '@chakra-ui/core';
// previously written code...

    const [photos, setPhotos] = useState([]);
    const toast = useToast();

    async function handlePhotoSelect (file) {
        setIsUploading(true);

        try {
            const result = await uploadPhoto(file);
            if (!result.success)
                throw new Error("Error Uploading photo");
                
            toast({
                duration: 5000,
                status: "success",
                isClosable: true,
                title: "Upload Complete.",
                description: "Saved your photo on Photato!",
            });
        } catch (err) {
            toast({
                duration: 9000,
                status: "error",
                isClosable: true,
                title: "Upload Error.",
                description: "Something went wrong when uploading your photo!",
            });
        }

        setIsUploading(false);
    };
// previously written code...

С вышеуказанными изменениями вы должны получить зеленое маленькое уведомление о тосте в нижней части экрана каждый раз, когда вы загружаете новое фото. Кроме того, обратите внимание, что в случае ошибки мы также называем тост с Статус: «Ошибка» Что покажет красный тост вместо зеленого.

Вот как мой успех тосты выглядят как:

Успех тост

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

Начните с помощью пряжа добавить React-Images в пределах Веб/ каталог. Тогда поп открыть src/app.container.js Файл и добавьте следующие биты:

import React, { useState, useEffect, useCallback } from 'react';
import Carousel, { Modal, ModalGateway } from "react-images";
// previously written code...

function AppContainer() {
    const [currentImage, setCurrentImage] = useState(0);
    const [viewerIsOpen, setViewerIsOpen] = useState(false);

    const openLightbox = useCallback((event, { photo, index }) => {
        setCurrentImage(index);
        setViewerIsOpen(true);
    }, []);

    const closeLightbox = () => {
        setCurrentImage(0);
        setViewerIsOpen(false);
    };

    // previously written code...
    return (
        // previously written code...
            
            
                {viewerIsOpen && (
                    
                         ({
                                ...x,
                                srcset: x.srcSet,
                                caption: x.title
                            }))}
                        />
                    
                )}
            
        // previously written code...
    );
}

Вот что делают изменения:

  • Импортирует необходимые компоненты из Реактивные изображения показать полноэкранную галерею.
  • Инициирует две государственные переменные CurrentImage и RewingerisOpen Отказ Мы увидим, как они скоро используются.
  • Создает мемуизорованную функцию обратного вызова Openlightbox Это срабатывает, когда пользователь нажимает на любую из фотографий из фотогалереи. При выполнении функция наборы RewingerisOpen True и устанавливает номер индекса фотографии, который нажал.
  • Другая функция CloseLightbox Создан, что по существу закрывает полноэкранную галерею.
  • В методе рендера, если RewingerisOpen это правда Мы визуализируем модальный лайтбокс, содержащий Карусель Компонент из реактивных изображений lib.
  • Модал Компонент получает опоры OnClose = {Clockelightbox} Так что пользователь может закрыть полноэкранную галерею.
  • Мы передаем CurrentImage Номер индекса к нему, чтобы он знал, какое фото будет отображаться первым, и кроме того, мы преобразуем все фотографии из галереи и передаем ее на карусель, чтобы пользователь мог провести все фотографии в полноэкранном режиме.

Конечный результат:

Полноэкранная галерея
  • То, что мы построили в этом путешествии, это полное и функциональное приложение, но есть много места для улучшения. Архитектура, структура файла-папок, реализация Все эти вещи следует учитывать для рефакторирования как наших клиентских и серверных побочных приложений. Я хотел бы, чтобы вы взяли его как домашнее задание и добавить блок и/или тестирование интеграции на кодовую базу.
  • Chakra UI – это многообещающий новый инструмент и имеет многочисленные компоненты, которые будут трудно прикрывать в одном посте, поэтому я настоятельно рекомендую вам пройти через свои документы, чтобы узнать больше.
  • В наши дни сохраняют загруженный контент на одном диске, где работает ваше приложение, несколько нахмурится. К счастью, MULTER имеет много удобных 3-го вечеринок, которые позволят вам загружать файлы непосредственно на внешние хранилища, такие как S3. Если вы когда-нибудь развертываете приложение вашего сервера на хостинг-сервисах, таких как Zeit Now или NetLify, они пригодятся в порядке.