React — это популярная JavaScript-библиотека, предназначенная для создания пользовательских интерфейсов. Её главная особенность — компонентная архитектура, что позволяет разработчикам легко создавать и управлять сложными веб-приложениями. В этой статье мы рассмотрим, как создать React-приложение и выполнить его деплой на сервер с использованием Timeweb Cloud.
Для примера мы создадим простой TO-DO-лист, который позволит пользователям добавлять задачи, организовывать их по колонкам и управлять ими.
Перед началом работы убедитесь, что у вас установлен Node.js. Если его нет, скачайте последнюю версию с официального сайта Node.js и установите, следуя инструкциям на экране.
С установленным Node.js можно приступать к созданию React-приложения.
Консоль: Запустите консоль Windows или другой терминал на вашем устройстве.
Выбор пути: Переместитесь в папку, где будет размещен проект. Например, чтобы перейти на рабочий стол, выполните команду:
cd Desktop
Создание проекта: Введите команду для создания нового React-проекта:
npx create-react-app to-do-list
Название to-do-list
можно заменить на любое другое по вашему выбору.
Редактор: Откройте проект в текстовом редакторе.
Во время разработки убедитесь, что все команды для установки зависимостей и запуска проекта выполняются в корневой директории проекта. Если появляются ошибки, убедитесь, что находитесь в правильной папке. Все необходимые файлы будут размещены в каталоге src
.
Для локального запуска приложения введите команду npm start
. Эта команда откроет проект в новой вкладке браузера.
Перед началом написания кода установим лишь одну библиотеку — fortawesome
, которая предназначена для того, чтобы использовать различные иконки.
npm install --save @fortawesome/react-fontawesome @fortawesome/free-solid-svg-icons @fortawesome/fontawesome-svg-core
Откройте файл App.js
и напишите код для основного компонента приложения.
import React, { useState, useEffect } from 'react';
import Column from './Column';
import './App.css';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faPlus } from '@fortawesome/free-solid-svg-icons';
function App() {
// Функция для загрузки данных из localStorage или начальные данные
const loadColumns = () => {
const savedColumns = localStorage.getItem('columns');
return savedColumns ? JSON.parse(savedColumns) : [
{ id: 1, name: 'Нужно сделать', tasks: [] },
{ id: 2, name: 'Делается', tasks: [] },
{ id: 3, name: 'Готово', tasks: [] },
];
};
// Состояние для хранения колонок, инициализируется данными из loadColumns
const [columns, setColumns] = useState(loadColumns);
// useEffect для сохранения колонок в localStorage при их изменении
useEffect(() => {
localStorage.setItem('columns', JSON.stringify(columns));
}, [columns]);
// Функция для добавления новой колонки
const addColumn = () => {
const newColumnId = Date.now(); // Уникальный идентификатор для новой колонки
setColumns([...columns, { id: newColumnId, name: 'Новая колонка', tasks: [] }]);
};
// Функция для удаления колонки по её идентификатору
const removeColumn = (id) => {
setColumns(columns.filter(column => column.id !== id));
};
// Функция для добавления новой задачи в колонку
const addTask = (columnId) => {
setColumns(columns.map(column =>
column.id === columnId
? { ...column, tasks: [...column.tasks, { id: Date.now(), text: 'Без названия', isEditing: false }] }
: column
));
};
// Функция для обновления текста задачи
const updateTask = (columnId, taskId, newText) => {
setColumns(columns.map(column =>
column.id === columnId
? {
...column,
tasks: column.tasks.map(task =>
task.id === taskId ? { ...task, text: newText, isEditing: false } : task
)
}
: column
));
};
// Функция для удаления задачи из колонки
const removeTask = (columnId, taskId) => {
setColumns(columns.map(column =>
column.id === columnId
? { ...column, tasks: column.tasks.filter(task => task.id !== taskId) }
: column
));
};
// Функция для начала редактирования задачи
const startEditingTask = (columnId, taskId) => {
setColumns(columns.map(column =>
column.id === columnId
? {
...column,
tasks: column.tasks.map(task =>
task.id === taskId ? { ...task, isEditing: true } : task
)
}
: column
));
};
// Функция для перемещения задачи между колонками
const moveTask = (fromColumnId, toColumnId, taskId) => {
if (fromColumnId === toColumnId) return; // Нельзя перемещать задачу в ту же колонку
const fromColumn = columns.find(column => column.id === fromColumnId);
const toColumn = columns.find(column => column.id === toColumnId);
if (!fromColumn || !toColumn) return;
const taskToMove = fromColumn.tasks.find(task => task.id === taskId);
setColumns(columns.map(column => {
if (column.id === fromColumnId) {
return { ...column, tasks: column.tasks.filter(task => task.id !== taskId) };
}
if (column.id === toColumnId) {
return { ...column, tasks: [...column.tasks, taskToMove] };
}
return column;
}));
};
return (
<div className="app-container">
<div className="columns-container">
{columns.map(column => (
<Column
key={column.id}
column={column}
removeColumn={removeColumn}
addTask={addTask}
removeTask={removeTask}
updateTask={updateTask}
startEditingTask={startEditingTask}
moveTask={moveTask}
/>
))}
<button className="add-column-btn" onClick={addColumn}>
<FontAwesomeIcon icon={faPlus} />
</button>
</div>
</div>
);
}
export default App;
Теперь создайте новый файл Column.js
. В нём будет находиться код для создания колонок, в которые мы будем помещать задачи:
import React, { useState, useRef, useEffect } from 'react';
import Task from './Task';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faTrash, faPencil } from '@fortawesome/free-solid-svg-icons';
function Column({ column, removeColumn, addTask, removeTask, updateTask, startEditingTask, moveTask }) {
const [isEditingName, setIsEditingName] = useState(false);
const [columnName, setColumnName] = useState(column.name);
const inputRef = useRef(null);
useEffect(() => {
if (isEditingName && inputRef.current) {
inputRef.current.focus(); // Фокус на input при начале редактирования имени колонки
}
}, [isEditingName]);
useEffect(() => {
setColumnName(column.name); // Обновление локального состояния при изменении имени колонки
}, [column.name]);
// Обработка события переноса задачи в колонку
const handleDrop = (e) => {
e.preventDefault();
const taskId = e.dataTransfer.getData('taskId');
const fromColumnId = parseInt(e.dataTransfer.getData('fromColumnId'), 10);
if (fromColumnId && taskId) {
moveTask(fromColumnId, column.id, parseInt(taskId, 10));
}
};
// Завершение редактирования имени колонки и обновление его
const handleNameChange = () => {
setIsEditingName(false);
if (columnName.trim() === '') {
setColumnName('Без названия');
} else {
updateColumnName(column.id, columnName);
}
};
// Начало редактирования имени колонки
const startEditingColumnName = () => {
setIsEditingName(true);
};
// Обновление имени колонки в родительском компоненте
const updateColumnName = (columnId, newName) => {
setColumnName(newName);
// Можно добавить метод в App.js для обновления названия колонки
};
return (
<div className="column" data-id={column.id} onDragOver={(e) => e.preventDefault()} onDrop={handleDrop}>
<div className="column-header">
{isEditingName ? (
<input
ref={inputRef}
type="text"
value={columnName}
onChange={(e) => setColumnName(e.target.value)}
onBlur={handleNameChange}
onKeyPress={(e) => {
if (e.key === 'Enter') {
handleNameChange();
}
}}
className="column-name-input"
/>
) : (
<h3 className="column-name">{columnName}</h3>
)}
<div className="column-actions">
<button onClick={startEditingColumnName} className="edit-column-btn">
<FontAwesomeIcon icon={faPencil} />
</button>
<button onClick={() => removeColumn(column.id)} className="remove-column-btn">
<FontAwesomeIcon icon={faTrash} />
</button>
</div>
</div>
{column.tasks.map(task => (
<Task
key={task.id}
task={task}
removeTask={() => removeTask(column.id, task.id)}
updateTask={(newText) => updateTask(column.id, task.id, newText)}
startEditingTask={() => startEditingTask(column.id, task.id)}
/>
))}
<button className="add-task-btn" onClick={() => addTask(column.id)}>Добавить задачу</button>
</div>
);
}
export default Column;
Также создайте файл Task.js
и внутри него напишите код, чтобы создавать задачи:
import React, { useState, useRef, useEffect } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faTrash, faPencil } from '@fortawesome/free-solid-svg-icons';
function Task({ task, removeTask, updateTask }) {
const [isEditing, setIsEditing] = useState(false);
const [taskText, setTaskText] = useState(task.text);
const inputRef = useRef(null);
useEffect(() => {
if (isEditing && inputRef.current) {
inputRef.current.focus(); // Фокус на input при начале редактирования задачи
}
}, [isEditing]);
// Начало редактирования задачи
const handleEdit = () => {
setIsEditing(true);
};
// Сохранение изменений задачи
const handleSave = () => {
setIsEditing(false);
if (taskText.trim() === '') {
setTaskText('Без названия');
}
updateTask(taskText);
};
// Обработка события перетаскивания задачи
const handleDragStart = (e) => {
e.dataTransfer.setData('taskId', task.id);
e.dataTransfer.setData('fromColumnId', e.target.closest('.column').dataset.id);
};
return (
<div
className="task"
draggable
onDragStart={handleDragStart}
>
{isEditing ? (
<input
ref={inputRef}
type="text"
value={taskText}
onChange={(e) => setTaskText(e.target.value)}
onBlur={handleSave}
onKeyPress={(e) => {
if (e.key === 'Enter') {
handleSave();
}
}}
className="task-input"
/>
) : (
<span className="task-text">{taskText}</span>
)}
<div className="task-actions">
<button onClick={handleEdit} className="edit-task-btn">
<FontAwesomeIcon icon={faPencil} />
</button>
<button onClick={removeTask} className="remove-task-btn">
<FontAwesomeIcon icon={faTrash} />
</button>
</div>
</div>
);
}
export default Task;
После завершения разработки вы можете снова запустить приложение командой npm start
и убедиться, что оно работает корректно. Приложение позволяет добавлять, редактировать и удалять задачи и колонки, а также перемещать задачи между колонками. Данные сохраняются локально, поэтому при перезагрузке страницы ничего не пропадет.
Теперь, когда приложение готово, его нужно развернуть на сервере. В этом примере мы воспользуемся Timeweb Cloud.
Для начала необходимо загрузить файлы проекта в систему контроля версий Git. Вы можете использовать любую платформу для хранения репозиториев, но в данном случае мы будем использовать проект, размещенный на GitHub.
Чтобы развернуть React-приложение, воспользуемся сервисом Apps от Timeweb Cloud.
Deployment successfully completed
.В этой статье мы разобрали, как создать React-приложение и развернуть его на сервере с помощью Timeweb Cloud. Следуя этим шагам, вы сможете быстро освоить процесс деплоя и использовать платформу для размещения своих веб-приложений.