Partilhar via


Criar um aplicativo React no Visual Studio

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

Pré-requisitos

Certifique-se de instalar 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 > React JSX Component File, 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>
      );
    }
    

    Este componente exibe um cabeçalho, que você substituirá mais tarde.

    Em seguida, conecte esse componente no aplicativo. App.jsx é o principal componente carregado que representa a aplicação de lista to-do. Este componente é usado no arquivo main.jsx.

  6. No Explorador de Soluções, abra App.jsx, remova todas as importações do topo e limpe o conteúdo da instrução de retorno. O arquivo deve ter a seguinte aparência.

    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, limpe os arquivos CSS.

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

  9. Abra Index.css e remova todo o conteúdo, exceto os estilos para :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 de lista to-do à aplicação

Você pode deixar o aplicativo em execução. À medida que fazes alterações, o aplicativo é atualizado automaticamente com o conteúdo mais recente utilizando o suporte de substituição de módulos dinâmicos do Vite. Algumas ações, como adicionar pastas ou renomear arquivos, exigem que você pare a depuração e reinicie o aplicativo, mas, em geral, você pode deixá-lo em execução em segundo plano enquanto 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 IU necessária para mostrar e gerir as entradas da lista 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, conecte o botão Adicionar. Use o gancho useState React 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 seguinte instrução de importação ao TodoList.jsx para importar useState.

    import { useState } from 'react'
    
  3. Em seguida, use esse gancho para criar as variáveis de estado. Adicione o seguinte código 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 automaticamente reprocessa o componente.

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

    No Reagir, quando você exibe uma lista de itens, precisa adicionar uma chave para identificar exclusivamente cada item na lista. Esse recurso é explicado detalhadamente nos documentos do React em Listas de renderização, mas aqui você aprenderá o básico. Você tem uma lista de to-do itens para exibir e precisa associar uma chave exclusiva para cada item. A chave para 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 durante toda a existência desses valores. Você usará randomUUID() para criar uma ID exclusiva para cada item to-do.

  4. Crie TodoList.jsx usando um UUID como 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ê notará que não pode digitar no elemento de entrada todo. Isso ocorre porque o elemento input está vinculado a newTaskText, que foi inicializado para uma cadeia de caracteres em branco. Para permitir que os usuários adicionem novas tarefas, você precisa manipular 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 return 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 através event.target.value, e esse valor é usado para atualizar o valor para a 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 atualize o botão Adicionar para incluir onClick={addTask}. Esse código conecta o evento à função que manipula esse evento. Depois disso, você poderá adicionar uma nova tarefa à lista de tarefas. Novas tarefas são adicionadas ao final da lista. Para tornar este 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 suportar excluir, mover para cima e mover para baixo e, em seguida, atualize a marcação para mostrar um botão para cada ação. Adicione o seguinte código 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 recebe 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 assumem o índice do item porque esse trabalho é específico para a ordem do item. Tanto moveTaskUp() quanto moveTaskDown() usam de atribuição de desestruturação de matriz para trocar a tarefa selecionada com seu vizinho.

  7. Em seguida, atualize o modo de exibição para incluir esses três botões. Atualize a instrução de retorno 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 que discutimos anteriormente. Você está usando caracteres Unicode como ícones nos botões. Na marcação, há alguns atributos adicionados para suportar a adição de CSS mais tarde. Você também pode notar o uso de atributos de ária para melhorar a acessibilidade, que são opcionais, mas altamente recomendados. Se abrir a aplicação, ela deve ter a seguinte aparência.

    Captura de ecrã a mostrar a aplicação em execução e a mostrar uma lista

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

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

    Essas funções funcionam, mas você pode refatorar para criar um componente reutilizável para exibir os itens to-do. A marcação para o item to-do é transferida para um novo componente, TodoItem. Como a gestão da lista fica no componente Todo, pode passar 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 se abre, selecione o React JSX Component File, dê-lhe o nome TodoItem e selecione Adicionar.

  10. Adicione o seguinte código ao TodoItem.

    Neste código, passas a tarefa e as funções de retorno 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 dele você declara o PropTypes. Os adereços são usados para passar dados de um componente pai para componentes filho. Para obter mais informações sobre adereços, consulte Passando adereços para um componente – Reagir. Como as funções delete e move estão sendo passadas como retornos de chamada, o manipulador de onClick precisa ser atualizado para chamar 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 para essa tarefa. Ao executar o aplicativo, você não deve 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 suportadas, é hora de começar a adicionar algum estilo a isso para torná-lo agradável. Comece adicionando um link no Index.html para uma família de fontes, Inter, que você usará para este aplicativo. Em Index.html, existem alguns outros itens que precisam ser limpos. Especificamente, o título deve ser atualizado e você deseja substituir o arquivo de vite.svg que é usado atualmente como o ícone.

  12. Atualize Index.html com o seguinte conteúdo.

    <!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 ficheiro checkmark-square.svg foi adicionado à pasta pública. Este é um SVG da imagem quadrada da marca de verificação FluentUI, que você pode baixar diretamente. (Há um pacote que você pode usar para uma experiência mais integrada, mas isso está fora do escopo deste artigo.)

    Em seguida, atualize os estilos do componente TodoList.

  14. Na pasta components, adicione um novo arquivo CSS chamado TodoList.css. Você pode clicar com o botão direito do mouse no projeto e selecionar > Adicionar Novo Item e, em seguida, selecionar Folha de Estilos. Dê ao arquivo o nome TodoList.css.

  15. Adicione o seguinte código 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 ter a seguinte aparência.

    Captura de ecrã a mostrar a versão final da aplicação em execução.

    Agora você criou uma aplicação de lista funcional to-do que armazena os itens to-do na memória. A partir deste ponto, você pode atualizar o aplicativo para armazenar os itens to-do em localStorage/IndexedDbou integrá-lo a um banco de dados do lado do servidor, ou outro back-end, para um 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