Как загружать файлы в React с помощью NodeJS и Express
2 года назад·3 мин. на чтение
В этой статье рассмотрим как загрузить файлы в React использованием Express в качестве бэкенда.
Создание бэкенда на Express
Во-первых, мы создаем POST API с использованием NodeJS и Express, который поможет нам загружать файлы (изображения, pdf и т. д.) на внутренний сервер.Настройка серверного проекта
Давайте настроим бэкенд проект NodeJS, выполнив следующие команды одну за другой.mkdir fileupload cd fileupload npm init -y
Установка пакетов
Теперь нам нужно установить четыре пакета:express
, express-fileupload
, cors
и nodemon
.
Выполните приведенную ниже команду, чтобы установить пакеты.
Теперь откройте папкуnpm i express express-fileupload cors nodemon
fileupload
в вашем любимом редакторе кода и создайте новый файл с именем server.js
.
Добавьте следующий код в файл server.js
.
В приведенном выше коде мы сначала импортировали три пакета:// server.js const express = require('express'); const fileUpload = require('express-fileupload'); const cors = require('cors') const app = express(); // middleware app.use(express.static('public')); // для доступа к файлам в папке public app.use(cors()); app.use(fileUpload()); // API для загрузки файлов app.post('/upload', (req, res) => { if (!req.files) { return res.status(500).send({ msg: "file is not found" }) } const myFile = req.files.file; // метод mv() помещает файл в папку public myFile.mv(`${__dirname}/public/${myFile.name}`, function (err) { if (err) { console.log(err) return res.status(500).send({ msg: "Error occurred" }); } // возвращаем ответ с именем файла и его расположением return res.send({name: myFile.name, path: `/${myFile.name}`}); }); }) app.listen(4500, () => { console.log('server is running at port 4500'); })
express
, express-fileupload
и cors
, а затем создали приложение express вызвав функцию express()
Наш маршрут POST API - /upload
.
Мы помещаем файлы в общую папку public
. Для этого нам нужно создать папку public
внутри нашего бэкенд-проекта.
Добавление скриптов
Чтобы запускать и перезапускать сервер мы используемnodemon
. Откройте файл package.json
и добавьте следующий код в scripts
.
Теперь запустите сервер, выполнив команду"server": "nodemon server.js"
npm run server
в терминале.
Создание приложения React
Давайте создадим новое React приложение, выполнив следующую команду.Теперь измените текущий рабочий каталог, выполнив приведенную ниже команду.npx create-react-app react-fileupload
cd react-fileupload
Установка библиотеки Axios
Нам также необходимо установить клиентскую библиотекуaxios
, которая используется для выполнения http-запросов.
npm i axios
Создание компонента загрузки файлов
Откройте папкуreact-fileupload
в своем любимом редакторе кода и создайте новый файл с именем fileupload.js
внутри папки src
.
Теперь добавьте следующий код.
В приведенном выше коде мы использовали хуки react для управления состоянием, и у нас есть две функции:// fileupload.js import React, { useRef, useState } from 'react'; import axios from 'axios'; function FileUpload() { // для хранения загруженного файла const [file, setFile] = useState(''); // для хранения ответа от бекенда const [data, getFile] = useState({ name: "", path: "" }); const [progress, setProgess] = useState(0); // progessbar const el = useRef(); // для доступа к инпуту const handleChange = (e) => { setProgess(0) const file = e.target.files[0]; // доступ к файлу console.log(file); setFile(file); // сохранение файла } const uploadFile = () => { const formData = new FormData(); formData.append('file', file); // добавление файла axios.post('http://localhost:4500/upload', formData, { onUploadProgress: (ProgressEvent) => { let progress = Math.round( ProgressEvent.loaded / ProgressEvent.total * 100 ) + '%'; setProgess(progress); } }).then(res => { console.log(res); getFile({ name: res.data.name, path: 'http://localhost:4500' + res.data.path }) }).catch(err => console.log(err)) } return ( <div> <div className="file-upload"> <input type="file" ref={el} onChange={handleChange} /> <div className="progessBar" style={{ width: progress }}> {progress} </div> <button onClick={uploadFile} className="upbutton"> Upload </button> <hr /> {/* для показа полученного изображения */} {data.path && <img src={data.path} alt={data.name} />} </div> </div> ); } export default FileUpload;
handleChange
и uploadFile
.
handleChange
вызывается после того, как пользователь выбрал файл.
Функция uploadFile()
используется для загрузки файла в наш /upload
api.
Существует также индикатор выполнения, который показывает прогресс загрузки файла на сервер, а также мы отображаем изображение после получения ответа от сервера.
Добавление стилей css
Добавьте следующие стили в файлApp.css
.
Теперь импортируйте компонент.App { margin: 2rem auto; max-width: 800px; } img { width: 500px; height: 500px; object-fit: contain; } .progessBar { height: 1rem; width: 0%; background-color: rgb(68, 212, 231); color: white; padding:2px } .file-upload { box-shadow: 2px 2px 2px 2px #ccc; padding: 2rem; display: flex; flex-direction: column; justify-content: space-between; font-size: 1rem; } input , div , button{ margin-top: 2rem; } .upbutton { width: 5rem; padding: .5rem; background-color: #2767e9; color: aliceblue; font-size: 1rem; cursor: pointer; }
FileUpload
в файл App.js
.
Запустите приложение React, запустив// App.js import React from 'react'; import FileUpload from './fileupload'; import './App.css'; function App() { return ( <div className="App"> <FileUpload /> </div > ); } export default App;
npm start
.Хук useContext - как использовать контекст в React?
2 года назад·4 мин. на чтение
useContext - это React хук, который позволяет вам читать и подписываться на контекст из вашего компонента.
API хука useContext
const value = useContext(SomeContext)
useContext(SomeContext)
ВызовитеuseContext
на верхнем уровне вашего компонента для чтения и подписки на контекст.
import { useContext } from 'react'; function MyComponent() { const theme = useContext(ThemeContext); // ...
Параметры
SomeContext
: Контекст, который вы ранее создали с помощьюcreateContext
. Сам контекст не содержит информации, он только представляет тип информации, которую вы можете предоставить или прочитать из компонентов.
Что возвращает useContext
?
useContext
возвращает значение контекста для вызывающего компонента. Оно определяется как значение, переданное ближайшему SomeContext.Provider
, расположенному выше вызывающего компонента в дереве. Если такого провайдера нет, то возвращаемое значение будет значением по умолчанию (defaultValue
), которое вы передали в createContext
для данного контекста. React автоматически перерисовывает компоненты, которые читают некоторый контекст, если он изменяется.
Использование контекста
Передача данных вглубь дерева
ВызовитеuseContext
на верхнем уровне вашего компонента для чтения и подписки на контекст.
import { useContext } from 'react'; function Button() { const theme = useContext(ThemeContext); // ...
useContext
возвращает значение контекста для переданного вами контекста. Чтобы определить значение контекста, React просматривает дерево компонентов и находит ближайший вышеуказанный провайдер контекста для данного контекста.
Чтобы передать контекст кнопке, оберните ее или один из ее родительских компонентов в соответствующий провайдер контекста:
Не имеет значения, сколько слоев компонентов находится между провайдером и кнопкой. Когда кнопка в любом месте формы вызываетfunction MyPage() { return ( <ThemeContext.Provider value="dark"> <Form /> </ThemeContext.Provider> ); } function Form() { // ... отрисовывает кнопки внутри себя ... }
useContext(ThemeContext)
, она получит значение "dark"
.
import { createContext, useContext } from 'react'; const ThemeContext = createContext(null); export default function MyApp() { return ( <ThemeContext.Provider value="dark"> <Form /> </ThemeContext.Provider> ) } function Form() { return ( <Panel title="Welcome"> <Button>Sign up</Button> <Button>Log in</Button> </Panel> ); } function Panel({ title, children }) { const theme = useContext(ThemeContext); const className = 'panel-' + theme; return ( <section className={className}> <h1>{title}</h1> {children} </section> ) } function Button({ children }) { const theme = useContext(ThemeContext); const className = 'button-' + theme; return ( <button className={className}> {children} </button> ); }
Обновление данных, переданных через контекст
Часто бывает необходимо, чтобы контекст менялся с течением времени. Чтобы обновить контекст, вам нужно объединить его с состоянием. Объявите переменную state в родительском компоненте и передайте текущее состояние в качестве значения контекста провайдеру.Теперь любая кнопка внутри провайдера будет получать текущее значение темы. Если вы вызоветеunction MyPage() { const [theme, setTheme] = useState('dark'); return ( <ThemeContext.Provider value={theme}> <Form /> <Button onClick={() => { setTheme('light'); }}> Switch to light theme </Button> </ThemeContext.Provider> ); }
setTheme
для обновления значения темы, которое вы передаете провайдеру, все компоненты Button
будут заново отображаться с новым значением "light"
.
Указание значения по умолчанию
Если React не может найти ни одного провайдера данного контекста в родительском дереве, значение контекста, возвращаемое функциейuseContext()
, будет равно значению по умолчанию, которое вы указали при создании контекста:
Значение по умолчанию никогда не изменяется. Если вы хотите обновить контекст, используйте его вместе с состоянием, как описано выше. Часто вместоconst ThemeContext = createContext(null);
null
можно использовать какое-то более значимое значение по умолчанию, например:
Таким образом, если вы случайно отобразите какой-то компонент без соответствующего провайдера, он не сломается. Это также поможет вашим компонентам хорошо работать в тестовой среде без установки большого количества провайдеров в тестах. В приведенном ниже примере кнопка "Toggle theme" всегда светлая, потому что она находится вне любого провайдера контекста темы, а значение контекстной темы по умолчанию -const ThemeContext = createContext('light');
'light'
.
import { createContext, useContext, useState } from 'react'; const ThemeContext = createContext('light'); export default function MyApp() { const [theme, setTheme] = useState('light'); return ( <> <ThemeContext.Provider value={theme}> <Form /> </ThemeContext.Provider> <Button onClick={() => { setTheme(theme === 'dark' ? 'light' : 'dark'); }}> Toggle theme </Button> </> ) } function Form({ children }) { return ( <Panel title="Welcome"> <Button>Sign up</Button> <Button>Log in</Button> </Panel> ); } function Panel({ title, children }) { const theme = useContext(ThemeContext); const className = 'panel-' + theme; return ( <section className={className}> <h1>{title}</h1> {children} </section> ) } function Button({ children, onClick }) { const theme = useContext(ThemeContext); const className = 'button-' + theme; return ( <button className={className} onClick={onClick}> {children} </button> ); }
Переопределение контекста для части дерева
Вы можете переопределить контекст для части дерева, обернув эту часть в провайдер с другим значением.Вы можете вложить и переопределить провайдеров столько раз, сколько вам нужно.<ThemeContext.Provider value="dark"> ... <ThemeContext.Provider value="light"> <Footer /> </ThemeContext.Provider> ... </ThemeContext.Provider>
Оптимизация повторных рендерингов при передаче объектов и функций
Вы можете передавать любые значения через контекст, включая объекты и функции.Здесь значение контекста - это JavaScript объект с двумя свойствами, одно из которых - функция. Всякий раз, когдаfunction MyApp() { const [currentUser, setCurrentUser] = useState(null); function login(response) { storeCredentials(response.credentials); setCurrentUser(response.user); } return ( <AuthContext.Provider value={{ currentUser, login }}> <Page /> </AuthContext.Provider> ); }
MyApp
ререндерится (например, при обновлении маршрута), это будет другой объект, указывающий на другую функцию, поэтому React также придется перерендерить все компоненты в глубине дерева, которые вызывают useContext(AuthContext)
.
В небольших приложениях это не является проблемой. Однако нет необходимости перерисовывать их, если базовые данные, такие как currentUser
, не изменились. Чтобы помочь React воспользоваться этим фактом, вы можете обернуть функцию входа в систему в useCallback
и обернуть создание объекта в useMemo
.
В результате этого изменения, даже еслиimport { useCallback, useMemo } from 'react'; function MyApp() { const [currentUser, setCurrentUser] = useState(null); const login = useCallback((response) => { storeCredentials(response.credentials); setCurrentUser(response.user); }, []); const contextValue = useMemo(() => ({ currentUser, login }), [currentUser, login]); return ( <AuthContext.Provider value={contextValue}> <Page /> </AuthContext.Provider> ); }
MyApp
потребуется повторный рендеринг, компонентам, вызывающим useContext(AuthContext)
, не потребуется повторный рендеринг, если только currentUser
не изменился.