Compartilhar via


Criar um aplicativo React no Visual Studio

Neste tutorial, você criará um front-end do React para um aplicativo Web de lista de to-do usando o JavaScript e o Visual Studio 2022. O código para este aplicativo pode ser encontrado em ToDoJSWebApp.

Pré-requisitos

Instale o seguinte:

Criar o aplicativo React ToDo List

  1. No Visual Studio, selecione Arquivo > Novo > Projeto para abrir a caixa de diálogo Criar um Novo Projeto, selecione o modelo React App JavaScript e escolha Avançar.

    Captura de tela mostrando a escolha de um modelo.

  2. Nomeie o projeto TodoWebApp e selecione Criar.

    Isso cria o projeto JavaScript usando a ferramenta de linha de comando vite.

  3. No Gerenciador de Soluções, clique com o botão direito do mouse na pasta src e escolha Adicionar > Nova Pasta. e crie uma nova pasta chamada components.

    É uma convenção comum colocar componentes em uma pasta de componentes, mas isso não é necessário.

  4. Clique com o botão direito do mouse na nova pasta, selecione Adicionar > Arquivo de Componente React JSX, nomeie-o TodoListe clique em Adicionar.

    Captura de tela mostrando a adição de um componente JSX.

    Isso cria um novo arquivo JSX na pasta de componentes.

  5. Abra o componente TodoList e substitua o conteúdo padrão pelo seguinte:

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

    Esse componente exibe um cabeçalho, que você substituirá posteriormente.

    Em seguida, conecte esse componente no aplicativo. App.jsx é o componente principal que é carregado e que representa o aplicativo de lista de tarefas pendentes. Esse componente é usado no arquivo main.jsx.

  6. No Gerenciador de Soluções, abra App.jsx, remova todas as importações da parte superior e limpe o conteúdo da instrução de retorno. O arquivo deve ser semelhante ao seguinte.

    function App() {
      return (
        <>
          <TodoList />
        </>
      );
    }
    export default App;
    
  7. Para adicionar o componente TodoList, coloque o cursor dentro do fragmento e digite <TodoL RETURN. Isso adiciona o componente e a instrução de importação.

    Captura de tela mostrando a adição de um componente JSX ao aplicativo.

    Em seguida, desmarque os arquivos CSS.

  8. Abra App.css e exclua todo o conteúdo.

  9. Abra Index.css e remova todos os conteúdos, exceto os estilos de :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;
    }
    

Executar o aplicativo

Selecione o botão Iniciar Depuração na barra de ferramentas ou pressione o atalho de teclado F5.

O aplicativo é aberto em uma janela do navegador.

Captura de tela mostrando o aplicativo em execução no navegador.

Adicionar funções à lista de tarefas pendentes do aplicativo

Você pode deixar o aplicativo em execução. À medida que você faz alterações, o aplicativo é atualizado automaticamente com o conteúdo mais recente usando o suporte de substituição de módulo de acesso frequente do Vite. Algumas ações, como adicionar pastas ou renomear arquivos, exigem que você interrompa a depuração e reinicie o aplicativo, mas, em geral, você pode deixá-lo em execução em segundo plano à medida que desenvolve seu aplicativo. Abra o componente TodoList.jsx para que possamos começar a defini-lo.

  1. No Gerenciador de Soluções, abra TodoList.jsx e adicione a interface do usuário necessária para mostrar e gerenciar as entradas na lista de to-do. Substitua o conteúdo pelo seguinte código:

    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;
    

    O código anterior adiciona uma caixa de entrada para a nova tarefa to-do e um botão para enviar a entrada. Em seguida, você conecta o botão Adicionar. Use o useState react hook para adicionar duas variáveis de estado, uma para a tarefa que está sendo adicionada e outra para armazenar as tarefas existentes. Para este tutorial, as tarefas são armazenadas na memória e não no armazenamento persistente.

  2. Adicione a instrução de importação a seguir em TodoList.jsx para importar useState.

    import { useState } from 'react'
    
  3. Em seguida, use esse gancho para criar as variáveis de estado. Adicione o código a seguir na função TodoList acima da instrução return.

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

    Isso configura duas variáveis, tasks e newTaskText, para os dados e duas funções que você pode chamar para atualizar essas variáveis, setTasks e setNewTasks. Quando um valor para uma variável de estado é alterado, o React renderiza automaticamente o componente.

    Você está quase pronto para atualizar TodoList.jsx para mostrar os itens to-do como uma lista, mas há um conceito de React importante para aprender primeiro.

    No React, quando você exibe uma lista de itens, precisa adicionar uma chave para identificar exclusivamente cada item na lista. Esse recurso é explicado detalhadamente na documentação do React em Renderização de Listas, mas aqui você aprenderá as noções básicas. Você tem uma lista de to-do itens a serem exibidos e precisa associar uma chave exclusiva a cada item. A chave de cada item não deve ser alterada e, por esse motivo, você não pode usar o índice do item na matriz como a chave. Você precisa de uma ID que não mude ao longo do tempo de vida desses valores. Você usará randomUUID() para criar uma ID exclusiva para cada item to-do.

  4. Crie TodoList.jsx usando uma UUID como a chave para cada item to-do. Atualize TodoList.jsx com o código a seguir.

    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;
    

    Como os valores de ID são atribuídos fora da função TodoList, você pode ter certeza de que os valores não serão alterados se a página for renderizada novamente. Ao experimentar o aplicativo nesse estado, você observará que não é possível digitar no campo de entrada do to-do. Isso ocorre porque o elemento de entrada está associado a newTaskText, que foi inicializado para uma cadeia de caracteres em branco. Para permitir que os usuários adicionem novas tarefas, você precisa lidar com o evento onChange nesse controle. Você também precisa implementar o suporte ao botão Adicionar.

  5. Adicione as funções necessárias imediatamente antes da instrução de retorno na função 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();
    }
    

    Na função handleInputChanged, o novo valor do campo de entrada é passado por event.target.valuee esse valor é usado para atualizar o valor da variável newTaskText com setNewTaskText. Na função addTask, adicione a nova tarefa à lista de tarefas existentes usando setTasks e defina a ID do item como um novo valor UUID. Atualize o elemento de entrada para incluir onChange={handleInputChange} e atualizar o botão Adicionar para incluir onClick={addTask}. Esse código conecta o evento à função que manipula esse evento. Depois disso, você deve ser capaz de adicionar uma nova tarefa à lista de tarefas. Novas tarefas são adicionadas à parte inferior da lista. Para tornar esse aplicativo mais útil, você precisa adicionar suporte para excluir tarefas e mover uma tarefa para cima ou para baixo.

  6. Adicione as funções para dar suporte à exclusão, mova para cima e mova para baixo e atualize a marcação para mostrar um botão para cada ação. Adicione o código a seguir na função TodoList acima da instrução 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);
        }
    }
    

    A função delete usa a ID da tarefa e exclui essa da lista e usa o método Array filter() para criar uma nova matriz, excluindo o item selecionado e, em seguida, chama setTasks(). As outras duas funções recebem o índice do item porque esse trabalho é específico para a ordem dos itens. moveTaskUp() e moveTaskDown() usam a atribuição de desestruturação de matriz para trocar a tarefa selecionada com o respectivo vizinho.

  7. Em seguida, atualize o modo de exibição para incluir esses três botões. Atualize a instrução return para conter o seguinte.

    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>
    );
    

    Você adicionou os botões necessários para executar as tarefas discutidas anteriormente. Você está usando caracteres Unicode como ícones nos botões. Na marcação, há alguns atributos adicionados para dar suporte à adição de alguns CSS posteriormente. Você também pode observar o uso de atributos aria para melhorar a acessibilidade, os quais são opcionais, mas altamente recomendados. Se você executar o aplicativo, ele deverá ser semelhante à ilustração a seguir.

    Captura de tela mostrando o aplicativo em execução e mostrando uma lista

    Agora você deve ser capaz de executar o seguinte no aplicativo Web TODO.

    • Adicionar tarefa
    • Excluir tarefa
    • Mover a tarefa para cima
    • Mover a tarefa para baixo

    Essas funções funcionam, mas você pode refatorar para criar um componente reutilizável para exibir os itens de to-do. A marcação do item to-do entra em um novo componente, TodoItem. Como o gerenciamento da lista permanece no componente de tarefas pendentes, você pode passar as funções de retorno para os botões Excluir e Mover.

  8. Para começar, clique com o botão direito do mouse na pasta componentes no Gerenciador de Soluções e selecione Adicionar > Novo Item.

  9. Na caixa de diálogo que é aberta, selecione o arquivo de componente React JSX, dê o nome TodoItem e selecione Adicionar.

  10. Adicione o código a seguir ao TodoItem.

    Nesse código, você passa a tarefa e os callbacks como propriedades para este novo componente.

    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;
    

    O código anterior contém a marcação do componente Todo e, no final, você declara o PropTypes. Os Props são usados para passar dados de um componente pai para componentes filho. Para obter mais informações sobre Props, confira Passar Props para um componente – React. Como as funções excluir e mover estão sendo passadas como funções de retorno de chamada, o manipulador de eventos onClick precisa estar atualizado para acionar esses retornos de chamada.

  11. Adicione o código necessário. O código completo para TodoList que usa o componente TodoItem é mostrado aqui.

    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;
    

    Agora, o componente TodoItem é usado para renderizar cada item to-do. Observe que a chave está definida como task.id, que contém o valor UUID dessa tarefa. Ao executar o aplicativo, você não deverá ver nenhuma alteração na aparência ou no comportamento do aplicativo porque o refatorou para usar TodoItem.

    Agora que você tem todas as funções básicas com suporte, é hora de começar a adicionar alguns estilos a isso para torná-lo agradável. Comece adicionando um link em Index.html para uma família de fontes, o Inter, que você usará para este aplicativo. Em Index.html, há alguns outros itens que precisam ser limpos. Especificamente, o título deve ser atualizado e você deseja substituir o arquivo vite.svg que atualmente é usado como o ícone.

  12. Atualize Index.html com o conteúdo a seguir.

    <!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. Edite arquivo main.jsx para substituir root por main ao chamar createRoot.

    O código completo para main.jsx é mostrado aqui.

    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>,
    )
    

    Além dessas alterações, o arquivo checkmark-square.svg foi adicionado à pasta pública. Este é um SVG da imagem quadrada de marca de seleção FluentUI, que você pode baixar diretamente. (Há um pacote que você pode usar para uma experiência mais integrada, mas que está fora do escopo deste artigo.)

    Em seguida, atualize os estilos do componente TodoList.

  14. Na pasta de componentes, adicione um novo arquivo CSS chamado TodoList.css. Clique com o botão direito do mouse no projeto e selecione Adicionar > Novo Item e selecione Folha de Estilos. Dê ao arquivo o nome TodoList.css.

  15. Adicione o código a seguir ao 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. Em seguida, edite TodoList.jsx para adicionar a seguinte importação na parte superior do arquivo.

    import './TodoList.css';
    
  17. Atualize o navegador depois de salvar as alterações. Isso deve melhorar o estilo do aplicativo. O aplicativo deve ser semelhante ao seguinte.

    Captura de tela mostrando a versão final do aplicativo em execução.

    Agora você criou um aplicativo de lista to-do funcionando que armazena os itens to-do na memória. A partir desse ponto, você pode atualizar o aplicativo para armazenar os itens to-do em localStorage/IndexedDb, ou integrá-lo a um banco de dados do lado do servidor ou outro back-end, para armazenamento mais permanente.

Resumo

Neste tutorial, você criou um novo aplicativo React usando o Visual Studio. O aplicativo consiste em uma lista de to-do, que inclui suporte para adicionar tarefas, excluir tarefas e reordená-las. Você criou dois novos componentes do React e os usou ao longo deste tutorial.

Recursos