Поделиться через


Создание приложения React в Visual Studio

В этом руководстве описано, как создать интерфейс React для веб-приложения списка to-do с помощью JavaScript и Visual Studio 2022. Код для этого приложения можно найти в ToDoJSWebApp.

Необходимые условия

Не забудьте установить следующее:

  • Visual Studio 2022 или более поздней версии. Перейдите на страницу загрузки Visual Studio, чтобы бесплатно установить ее.
  • npm (https://www.npmjs.com/), который входит в состав Node.js.

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

  1. В Visual Studio выберите > проект , чтобы открыть диалоговое окно "Создать проект", выберите шаблон приложения React React JavaScript, а затем нажмите кнопку Далее.

    снимок экрана, показывающий выбор шаблона.

  2. Назовите проект TodoWebApp и выберите Создать.

    Это создает проект JavaScript с помощью средства командной строки vite.

  3. В обозревателе решений щелкните правой кнопкой мыши папку src и выберите Добавить > Новая папка. и создайте новую папку с именем components.

    Это общее соглашение для размещения компонентов в папке компонентов, но это не обязательно.

  4. Щелкните правой кнопкой мыши новую папку, выберите > Добавить файл компонента JSX React, назовите его TodoListи щелкните Добавить.

    снимок экрана: добавление компонента JSX.

    При этом создается новый JSX-файл в папке компонентов.

  5. Откройте компонент TodoList и замените содержимое по умолчанию следующим образом:

    function TodoList() {
      return (
        <h2>TODO app contents</h2>
      );
    }
    

    Этот компонент отображает заголовок, который вы замените позже.

    Затем подключите этот компонент в приложении. App.jsx является основным загружаемым компонентом, представляющим приложение списка to-do. Этот компонент используется в файле main.jsx.

  6. В обозревателе решений откройте App.jsx, удалите все импорты из верхней части и удалите содержимое инструкции return. Файл должен выглядеть следующим образом.

    function App() {
      return (
        <>
          <TodoList />
        </>
      );
    }
    export default App;
    
  7. Чтобы добавить компонент TodoList, поместите курсор в фрагмент, а затем введите <TodoL RETURN. При этом добавляется компонент и инструкция импорта.

    снимок экрана: добавление компонента JSX в приложение.

    Затем удалите CSS-файлы.

  8. Откройте App.css и удалите все содержимое.

  9. Откройте Index.css и удалите все содержимое, кроме стилей для :root:

    :root {
      font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
      line-height: 1.5;
      font-weight: 400;
      color-scheme: light dark;
      color: rgba(255, 255, 255, 0.87);
      background-color: #242424;
    }
    

Запуск приложения

Нажмите кнопку Начать отладку на панели инструментов или нажмите сочетание клавиш F5.

Приложение открывается в окне браузера.

снимок экрана: приложение, работающее в браузере.

Добавление функций списка to-do в приложение

Вы можете оставить приложение запущенным. При внесении изменений приложение автоматически обновляется с последним содержимым благодаря поддержке горячей замены модулей Vite. Некоторые действия, такие как добавление папок или переименование файлов, требуют остановки отладки и перезапуска приложения, но в целом вы можете оставить его запущенным в фоновом режиме при разработке приложения. Откройте компонент TodoList.jsx, чтобы начать его определение.

  1. В обозревателе решений откройте TodoList.jsx и добавьте пользовательский интерфейс, необходимый для отображения записей списка to-do и управления ими. Замените содержимое следующим кодом:

    function TodoList() {
      return (
        <div>
          <h1>TODO</h1>
          <div>
            <input type="text" placeholder="Enter a task" required aria-label="Task text" />
            <button className="add-button" aria-label="Add task">Add</button>
          </div>
          <ol id="todo-list">
            <p>existing tasks will be shown here</p>
          </ol>
        </div>
      );
    }
    export default TodoList;
    

    Предыдущий код добавляет поле ввода для новой задачи to-do и кнопку для отправки входных данных. Затем вы подключите кнопку "Добавить". Используйте useState React, чтобы добавить две переменные состояния, одну для добавляемой задачи и другую для хранения существующих задач. В этом руководстве задачи хранятся в памяти, а не в постоянном хранилище.

  2. Добавьте следующую инструкцию импорта к TodoList.jsx, чтобы импортировать useState.

    import { useState } from 'react'
    
  3. Затем используйте этот хук для создания переменных состояния. Добавьте следующий код в функцию TodoList над оператором return.

    const [tasks, setTasks] = useState(["Drink some coffee", "Create a TODO app", "Drink some more coffee"]);
    const [newTaskText, setNewTaskText] = useState("");
    

    Это настраивает две переменные, tasks и newTaskTextдля данных и двух функций, которые можно вызвать для обновления этих переменных, setTasks и setNewTasks. При изменении значения переменной состояния React автоматически перерендеривает компонент.

    Вы почти готовы обновить TodoList.jsx, чтобы отобразить элементы to-do в виде списка, но сначала есть важная концепция React, которую нужно изучить.

    В React при отображении списка элементов необходимо добавить ключ для уникальной идентификации каждого элемента в списке. Данная функция подробно описана в референции React по отрисовке списков , но здесь вы научитесь основам. У вас есть список из to-do элементов, которые нужно отобразить, и необходимо присвоить уникальный ключ каждому элементу. Ключ для каждого элемента не должен изменяться, и по этой причине в качестве ключа нельзя использовать индекс элемента в массиве. Вам нужен идентификатор, который не будет изменяться в течение всего времени существования этих значений. Вы будете использовать randomUUID() для создания уникального идентификатора для каждого элемента to-do.

  4. Создайте TodoList.jsx, используя UUID в качестве ключа для каждого элемента to-do. Обновите TodoList.jsx с помощью следующего кода.

    import React, { useState } from 'react';
    
    const initialTasks = [
        { id: self.crypto.randomUUID(), text: 'Drink some coffee' },
        { id: self.crypto.randomUUID(), text: 'Create a TODO app' },
        { id: self.crypto.randomUUID(), text: 'Drink some more coffee' }
    ];
    
    function TodoList() {
        const [tasks, setTasks] = useState(initialTasks);
        const [newTaskText, setNewTaskText] = useState("");
    
        return (
            <article
                className="todo-list"
                aria-label="task list manager">
                <header>
                    <h1>TODO</h1>
                    <form className="todo-input" aria-controls="todo-list">
                        <input
                            type="text"
                            placeholder="Enter a task"
                            value={newTaskText} />
                        <button
                            className="add-button">
                            Add
                        </button>
                    </form>
                </header>
                <ol id="todo-list" aria-live="polite" aria-label="task list">
                    {tasks.map((task, index) =>
                        <li key={task.id}>
                            <span className="text">{task.text}</span>
                        </li>
                    )}
                </ol>
            </article>
        );
    }
    export default TodoList;
    

    Поскольку значения идентификаторов назначаются вне функции TodoList, вы можете быть уверены, что значения не изменятся, если страница будет перерендериться. При попытке приложения в этом состоянии вы заметите, что не удается ввести входной элемент todo. Это связано с тем, что входной элемент привязан к newTaskText, который был инициализирован пустой строкой. Чтобы разрешить пользователям добавлять новые задачи, необходимо обработать событие onChange на этом элементе управления. Также необходимо реализовать поддержку кнопки "Добавить".

  5. Добавьте необходимые функции непосредственно перед оператором return в функции TodoList.

    function handleInputChange(event) {
        setNewTaskText(event.target.value);
    }
    
    function addTask() {
        if (newTaskText.trim() !== "") {
            setTasks(t => [...t, { id: self.crypto.randomUUID(), text: newTaskText }]);
            setNewTaskText("");
        }
        event.preventDefault();
    }
    

    В функции handleInputChanged новое значение из поля ввода передается через event.target.value, а это значение используется для обновления значения переменной newTaskText с setNewTaskText. В функции addTask добавьте новую задачу в список существующих задач с помощью setTasks и задайте идентификатор элемента в качестве нового значения UUID. Обновите входной элемент, чтобы включить onChange={handleInputChange} и обновить кнопку "Добавить", чтобы включить onClick={addTask}. Этот код связывает событие с функцией, которая обрабатывает это событие. После этого вы сможете добавить новую задачу в список задач. Новые задачи добавляются в нижней части списка. Чтобы сделать это приложение более полезным, необходимо добавить поддержку для удаления задач и перемещения задачи вверх или вниз.

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

    function deleteTask(id) {
        const updatedTasks = tasks.filter(task => task.id != id);
        setTasks(updatedTasks);
    }
    
    function moveTaskUp(index) {
        if (index > 0) {
            const updatedTasks = [...tasks];
            [updatedTasks[index], updatedTasks[index - 1]] = [updatedTasks[index - 1], updatedTasks[index]];
            setTasks(updatedTasks);
        }
    }
    
    function moveTaskDown(index) {
        if (index < tasks.length) {
            const updatedTasks = [...tasks];
            [updatedTasks[index], updatedTasks[index + 1]] = [updatedTasks[index + 1], updatedTasks[index]];
            setTasks(updatedTasks);
        }
    }
    

    Функция удаления принимает идентификатор задачи, удаляет соответствующую задачу из списка и использует метод фильтра массива для создания нового массива, исключающего выбранный элемент, а затем вызывает setTasks(). Остальные две функции принимают индекс элемента, так как эта работа связана с упорядочением элементов. Как moveTaskUp(), так и moveTaskDown() используют деструктуризацию массива с помощью для обмена выбранной задачи с её соседом.

  7. Затем обновите представление, чтобы включить эти три кнопки. Обновите инструкцию возврата, чтобы она содержала следующее.

    return (
        <article
            className="todo-list"
            aria-label="task list manager">
            <header>
                <h1>TODO</h1>
                <form className="todo-input" onSubmit={addTask} aria-controls="todo-list">
                    <input
                        type="text"
                        required
                        autoFocus
                        placeholder="Enter a task"
                        value={newTaskText}
                        aria-label="Task text"
                        onChange={handleInputChange} />
                    <button
                        className="add-button"
                        aria-label="Add task">
                        Add
                    </button>
                </form>
            </header>
            <ol id="todo-list" aria-live="polite">
                {tasks.map((task, index) =>
                    <li key={task.id}>
                        <span className="text">{task.text}</span>
                        <button className="delete-button" onClick={() => deleteTask(task.id)}>
                            🗑️
                        </button>
                        <button className="up-button" onClick={() => moveTaskUp(index)}>
                            ⇧
                        </button>
                        <button className="down-button" onClick={() => moveTaskDown(index)}>
                            ⇩
                        </button>
                    </li>
                )}
            </ol>
        </article>
    );
    

    Вы добавили кнопки, необходимые для выполнения задач, которые мы обсуждали ранее. Символы Юникода используются в качестве значков на кнопках. В разметке есть некоторые атрибуты, добавленные для поддержки добавления некоторых CSS позже. Вы также можете обратить внимание на использование атрибутов aria для улучшения доступности, которые являются необязательными, но настоятельно рекомендуются. При запуске приложения он должен выглядеть так, как показано на следующем рисунке.

    Снимок экрана: запущенное приложение и отображение списка

    Теперь вы сможете выполнить следующие действия в веб-приложении TODO.

    • Добавление задачи
    • Удаление задачи
    • Перемещение задачи вверх
    • Перемещение задачи вниз

    Эти функции работают, но можно провести рефакторинг, чтобы создать переиспользуемый компонент отображения элементов to-do. Разметка для элемента to-do переходит в новый компонент TodoItem. Так как управление списком остается в компоненте Todo, вы можете передать обратные вызовы к кнопкам "Удалить" и "Переместить".

  8. Чтобы приступить к работе, щелкните правой кнопкой мыши папку компонентов в Проводнике решений и выберите Добавить > новый элемент.

  9. В открывшемся диалоговом окне выберите файл компонента React JSX, задайте ему имя TodoItem и выберите Добавить.

  10. Добавьте следующий код в TodoItem.

    В этом коде вы передаете задачу и обратные вызовы в качестве свойств в этот новый компонент.

    import PropTypes from 'prop-types';
    
    function TodoItem({ task, deleteTaskCallback, moveTaskUpCallback, moveTaskDownCallback }) {
        return (
            <li aria-label="task" >
                <span className="text">{task}</span>
                <button
                    type="button"
                    aria-label="Delete task"
                    className="delete-button"
                    onClick={() => deleteTaskCallback()}>
                    🗑️
                </button>
                <button
                    type="button"
                    aria-label="Move task up"
                    className="up-button"
                    onClick={() => moveTaskUpCallback()}>
                    ⇧
                </button>
                <button
                    type="button"
                    aria-label="Move task down"
                    className="down-button"
                    onClick={() => moveTaskDownCallback()}>
                    ⇩
                </button>
            </li>
        );
    }
    
    TodoItem.propTypes = {
        task: PropTypes.string.isRequired,
        deleteTaskCallback: PropTypes.func.isRequired,
        moveTaskUpCallback: PropTypes.func.isRequired,
        moveTaskDownCallback: PropTypes.func.isRequired,
    };
    
    export default TodoItem;
    

    Приведенный выше код содержит разметку из компонента Todo и в конце этого кода объявляется PropTypes. Пропсы используются для передачи данных от родительского компонента к дочерним компонентам. Дополнительные сведения о Props см. в разделе Передача Props компоненту — React. Так как функции удаления и перемещения передаются в качестве обратных вызовов, обработчик onClick необходимо обновить для вызова этих обратных вызовов.

  11. Добавьте необходимый код. Полный код для TodoList, использующий компонент TodoItem, показан здесь.

    import React, { useState } from 'react'
    import TodoItem from './TodoItem'
    
    const initialTasks = [
        { id: self.crypto.randomUUID(), text: 'Drink some coffee' },
        { id: self.crypto.randomUUID(), text: 'Create a TODO app' },
        { id: self.crypto.randomUUID(), text: 'Drink some more coffee' }
    ];
    
    function TodoList() {
        const [tasks, setTasks] = useState(initialTasks);
        const [newTaskText, setNewTaskText] = useState("");
        function handleInputChange(event) {
            setNewTaskText(event.target.value);
        }
        function addTask() {
            if (newTaskText.trim() !== "") {
                setTasks(t => [...t, { id: self.crypto.randomUUID(), text: newTaskText }]);
                setNewTaskText("");
            }
            event.preventDefault();
        }
        function deleteTask(id) {
            const updatedTasks = tasks.filter(task => task.id !== id);
            setTasks(updatedTasks);
        }
        function moveTaskUp(index) {
            if (index > 0) {
                const updatedTasks = [...tasks];
                [updatedTasks[index], updatedTasks[index - 1]] = [updatedTasks[index - 1], updatedTasks[index]];
                setTasks(updatedTasks);
            }
        }
        function moveTaskDown(index) {
            if (index < tasks.length) {
                const updatedTasks = [...tasks];
                [updatedTasks[index], updatedTasks[index + 1]] = [updatedTasks[index + 1], updatedTasks[index]];
                setTasks(updatedTasks);
            }
        }
        return (
            <article
                className="todo-list"
                aria-label="task list manager">
                <header>
                    <h1>TODO</h1>
                    <form onSubmit={addTask} aria-controls="todo-list">
                        <input
                            type="text"
                            required
                            placeholder="Enter a task"
                            value={newTaskText}
                            aria-label="Task text"
                            onChange={handleInputChange} />
                        <button
                            className="add-button"
                            aria-label="Add task">
                            Add
                        </button>
                    </form>
                </header>
                <ol id="todo-list" aria-live="polite">
                    {tasks.map((task, index) =>
                        <TodoItem
                            key={task.id}
                            task={task.text}
                            deleteTaskCallback={() => deleteTask(task.id)}
                            moveTaskUpCallback={() => moveTaskUp(index)}
                            moveTaskDownCallback={() => moveTaskDown(index)}
                        />
                    )}
                </ol>
            </article>
        );
    }
    
    export default TodoList;
    

    Теперь компонент TodoItem используется для отрисовки каждого элемента to-do. Обратите внимание, что для ключа задано значение task.id, которое содержит значение UUID для этой задачи. При запуске приложения вы не должны видеть никаких изменений в поведении или внешнего вида приложения, так как вы рефакторингировали его для использования TodoItem.

    Теперь, когда у вас есть все основные функции, пришло время приступить к добавлению оформления в проект, чтобы улучшить его внешний вид. Начните с добавления ссылки в Index.html для семейства шрифтов Inter, который будет использоваться для этого приложения. В Index.htmlесть и другие элементы, которые необходимо очистить. В частности, название должно быть обновлено, и вы хотите заменить файл vite.svg, который в настоящее время используется в качестве значка.

  12. Обновите Index.html со следующим содержимым.

    <!doctype html>
    <html lang="en">
        <head>
            <meta charset="UTF-8" />
            <link rel="icon" type="image/svg+xml" href="/checkmark-square.svg" />
            <meta name="viewport" content="width=device-width, initial-scale=1.0" />
            <title>TODO app</title>
            <link href='https://fonts.googleapis.com/css?family=Inter' rel='stylesheet'>
            <script type="module" defer src="/src/main.jsx"></script>
        </head>
        <body>
        </body>
    </html>
    
  13. Измените файл main.jsx, чтобы заменить rootmain при вызове createRoot.

    Полный код для main.jsx показан здесь.

    import { StrictMode } from 'react'
    import { createRoot } from 'react-dom/client'
    import App from './App.jsx'
    import './index.css'
    
    createRoot(document.querySelector('main')).render(
        <StrictMode>
            <App />
        </StrictMode>,
    )
    

    Помимо этих изменений, файл checkmark-square.svg был добавлен в общедоступную папку. Это SVG из квадратного изображения флажка FluentUI, который можно скачать напрямую. (Существует пакет, который можно использовать для более интегрированного интерфейса, но это вне области этой статьи.)

    Затем обновите стили компонента TodoList.

  14. В папке компонентов добавьте новый CSS-файл с именем TodoList.css. Вы можете щелкнуть правой кнопкой мыши на проекте и выбрать Добавить > новый элемент, а затем выбрать таблицу стилей. Присвойте файлу имя TodoList.css.

  15. Добавьте следующий код в TodoList.css.

    .todo-list {
        background-color: #1e1e1e;
        padding: 1.25rem;
        border-radius: 0.5rem;
        box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.3);
        width: 100%;
        max-width: 25rem;
    }
    
    .todo-list h1 {
        text-align: center;
        color: #e0e0e0;
    }
    
    .todo-input {
        display: flex;
        justify-content: space-between;
        margin-bottom: 1.25rem;
    }
    
    .todo-input input {
        flex: 1;
        padding: 0.625rem;
        border: 0.0625rem solid #333;
        border-radius: 0.25rem;
        margin-right: 0.625rem;
        background-color: #2c2c2c;
        color: #e0e0e0;
    }
    
    .todo-input .add-button {
        padding: 0.625rem 1.25rem;
        background-color: #007bff;
        color: #fff;
        border: none;
        border-radius: 0.25rem;
        cursor: pointer;
    }
    
    .todo-input .add-button:hover {
        background-color: #0056b3;
    }
    
    .todo-list ol {
        list-style-type: none;
        padding: 0;
    }
    
    .todo-list li {
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 0.625rem;
        border-bottom: 0.0625rem solid #333;
    }
    
    .todo-list li:last-child {
        border-bottom: none;
    }
    
    .todo-list .text {
        flex: 1;
    }
    
    .todo-list li button {
        background: none;
        border: none;
        cursor: pointer;
        font-size: 1rem;
        margin-left: 0.625rem;
        color: #e0e0e0;
    }
    
    .todo-list li button:hover {
        color: #007bff;
    }
    
    .todo-list li button.delete-button {
        color: #ff4d4d;
    }
    
    .todo-list li button.up-button,
    .todo-list li button.down-button {
        color: #4caf50;
    }
    
  16. Затем измените TodoList.jsx, чтобы добавить следующий импорт в верхней части файла.

    import './TodoList.css';
    
  17. Обновите браузер после сохранения изменений. Это должно улучшить стили приложения. Приложение должно выглядеть следующим образом.

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

    Теперь вы создали рабочее приложение списка to-do, которое хранит элементы to-do в памяти. С этого момента вы можете обновить приложение, чтобы сохранить элементы to-do в localStorage/IndexedDb, или интегрировать его с серверной базой данных или другой серверной системой для более постоянного хранения.

Сводка

В этом руководстве вы создали новое приложение React с помощью Visual Studio. Приложение состоит из списка to-do, который включает поддержку добавления задач, удаления задач и их переупорядочения. Вы создали два новых компонента React и использовали их в рамках этого руководства.

Ресурсы