19 сентября, Москва — конференция Business Day для IT-руководителей

Как создать и развернуть приложение на React: быстрый и простой деплой с Apps

Вадим Андоськин
Вадим Андоськин
Технический писатель
16 сентября 2024 г.
11
11 минут чтения
Средний рейтинг статьи: 5

React — это популярная JavaScript-библиотека, предназначенная для создания пользовательских интерфейсов. Её главная особенность — компонентная архитектура, что позволяет разработчикам легко создавать и управлять сложными веб-приложениями. В этой статье мы рассмотрим, как создать React-приложение и выполнить его деплой на сервер с использованием Timeweb Cloud.

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

Для примера мы создадим простой TO-DO-лист, который позволит пользователям добавлять задачи, организовывать их по колонкам и управлять ими.

Подготовка

Перед началом работы убедитесь, что у вас установлен Node.js. Если его нет, скачайте последнюю версию с официального сайта Node.js и установите, следуя инструкциям на экране.

Разработка

С установленным Node.js можно приступать к созданию React-приложения.

Инициализация проекта

  1. Консоль: Запустите консоль Windows или другой терминал на вашем устройстве.

  2. Выбор пути: Переместитесь в папку, где будет размещен проект. Например, чтобы перейти на рабочий стол, выполните команду:

cd Desktop
  1. Создание проекта: Введите команду для создания нового React-проекта:

npx create-react-app to-do-list

Название to-do-list можно заменить на любое другое по вашему выбору.

  1. Редактор: Откройте проект в текстовом редакторе.

Написание кода

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

Для локального запуска приложения введите команду npm start. Эта команда откроет проект в новой вкладке браузера.

Image1

Перед началом написания кода установим лишь одну библиотеку — 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 и убедиться, что оно работает корректно. Приложение позволяет добавлять, редактировать и удалять задачи и колонки, а также перемещать задачи между колонками. Данные сохраняются локально, поэтому при перезагрузке страницы ничего не пропадет.

Image6

Деплой React-приложения

Теперь, когда приложение готово, его нужно развернуть на сервере. В этом примере мы воспользуемся Timeweb Cloud.

Для начала необходимо загрузить файлы проекта в систему контроля версий Git. Вы можете использовать любую платформу для хранения репозиториев, но в данном случае мы будем использовать проект, размещенный на GitHub.

Запуск в Timeweb Cloud Apps

Чтобы развернуть React-приложение, воспользуемся сервисом Apps от Timeweb Cloud.

  1. Войдите в свой аккаунт Timeweb Cloud и создайте новый проект.

Image4

  1. В разделе «Apps» выберите тип «Frontend» и фреймворк React.

Image8

  1. Подключите свой репозиторий с проектом. 
  2. Выберите регион для минимизации задержки.

Image7

  1. В конфигурации выбираете лимит запросов, который нужен. В настройках приложения ничего не меняйте.

Image3

  1. В информации о приложении укажите его название и комментарий (если требуется).

Image5

  1. Нажмите «Запустить деплой» для автоматического разворачивания приложения на сервере. После успешного завершения процесса вы получите сообщение Deployment successfully completed.
  2. Теперь вы можете открыть приложение в браузере, используя предоставленный домен, и убедиться, что всё работает как нужно.

Image2

Заключение

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

Хотите внести свой вклад?
Участвуйте в нашей контент-программе за
вознаграждение или запросите нужную вам инструкцию
img-server
16 сентября 2024 г.
11
11 минут чтения
Средний рейтинг статьи: 5
Пока нет комментариев