E ai pessoal, tudo bem?

Continuando nossa série, agora que temos alguns conceitos mais básicos definidos, vamos começar a explorar a estrutura e sintaxe para uso do React, continuando nossa aplicação de tarefas (Todo List).

O repositório do projeto que vamos começar a trabalhar nesta parte da série está disponível em meu GitHub:

gustavobigardi/serie-react-todo-list
Repositorio contendo o aplicativo de exemplo Todo List, utilizado em minha série de artigos sobre React. …github.com

Estrutura do Projeto

Ao abrir nosso projeto no Visual Studio Code, temos a seguinte estrutura de pastas e arquivos:

create-react-app.

1 — Diretório node_modules

Neste diretórios ficam todos as dependências instaladas pelo Npm ou Yarn, que normalmente são configuradas no arquivo package.json (item 4) que veremos a seguir. Este diretório nunca é enviado para nosso repositório git, uma vez que tendo as dependências configuradas no package.json, basta executarmos o comando npm install para restaurar as dependências.

2 — Diretório public

Neste diretório ficam os arquivos estáticos que o React apenas copia ou injeta o resultado do build dos arquivos JavaScript e que normalmente não mudam durante o ciclo de desenvolvimento, sendo a página index.html, onde toda nossa árvore de objetos DOM será renderizada, o arquivo de manifesto (.manifest) informando ao browser detalhes da aplicação e recursos que ela consome do browser e o arquivo de ícone da aplicação, favicon.ico. Caso tenhamos algum arquivo estático que precisa apenas ser copiado para a saída do build final e não será utilizado pelo código JS, é neste diretório que podemos armazená-los.

3 — Diretório src

Neste diretório ficam armazenados todos os arquivos de código de nossa aplicação, em sua maioria, arquivos JavaScript, CSS (estilos), imagens e outros. Notem que de início temos um arquivo index.js… Ele é o ponto central de nossa aplicação, portanto, não apague ele. Temos outros arquivos como o App.js, que é um componente, assim como o App.test.js que é o arquivo contendo os testes unitários do component App.js. Normalmente, todo componente tem um aquivo .test.js associado com os testes unitários.

Após validar a estrutura, vamos entender melhor esta questão de componentes e arquivos do projeto.

4 — Arquivo package.json

Este arquivo contém toda a configuração de nosso projeto, como nome, versão, autor, dependências da aplicação, dependências em tempo de desenvolvimento, scripts ou aliases que são utilizados com o comando Npm, como npm start, npm run build e outros. Sempre que instalamos uma dependência, informamos ao Npm se queremos salvar no arquivo package.json a referência da dependência e sua respectiva versão para uso futuro, identificando se ela é uma dependêcia da aplicação, que será transpilada junto ao nosso código ou se é uma dependência de desenvolvimento, como uma biblioteca de build, de validação de código, que não precisa ser enviada ao servidor de produção. Fazemos isso adicionando, respectivamente, os parâmetros save e save-dev, utilizados com o comando npm install, como apresentado na imagem abaixo:

Comandos de save para dependênicias de runtime e desenvolvimento.

Um detalhe também que podemos configurar no arquivo package.json, são os scripts, onde podemos definir aliases para comandos mais complexos que utilizamos durante o desenvolvimento ou mesmo build da aplicação. No exemplo abaixo, defini o alias clear para que apague a pasta build, caso exista. Para executar, basta rodar o comando npm run clear.

Exemplo de um script criado no arquivo package.json para ser usado como alias no CLI do npm.

5 — Arquivo yarn.lock ou package-lock.json

Dependendo do gerenciador de pacotes que estiver utilizando, Npm ou Yarn, um destes arquivos será criado. Este arquivo é responsável por garantir uma instalação de consistente das dependências com versões compatíveis, inclusive é recomendado fazer o “commit” deste arquivo para seu repositório GIT. Para entender melhor como Npm lida com este arquivo, recomendo a leitura do artigo https://medium.com/trainingcenter/tudo-que-voc%C3%AA-queria-saber-sobre-o-package-lock-json-mas-estava-com-vergonha-de-perguntar-e70589f2855f.

6 — Arquivo serviceWorker.js

Vamos abordar este arquivo quando começarmos a falar de Progressive Web Applications, ou PWA. Por enquanto, podemos deixar ele de lado, mas precisamos lembrar dele mais adiante na série pois podemos agregar muito valor a nossa aplicação transformando ela em um PWA.

Arquivo index.js

Dei um destaque para este arquivo, pois ele é o ponto de entrada de nossa aplicação. Ele possui o conteúdo a seguir:

Estrutura inicial do arquivo index.js, ponto de entrada da aplicação.

Ele faz o import da biblioteca React, que fará a renderização da árvore de componentes em momória, o Virtual DOM, assim como da biblioteca ReactDOM, responsável por renderizar as atualizações da árvore virtual no DOM do browser.

Outras importações como arquivos CSS estão neste arquivo também, quando utilizados em comum por toda a aplicação.

Notem que temos o import de um componente chamado App, que em nosso projeto, é o único e principal componente da aplicação. É o nó raiz dela, sendo renderizado na linha 7 pelo ReactDOM.render.

Outro import realizado neste arquivo é o do serviceWorker, mas que iremos remover por enquanto. Futuramente iremos abordar este arquivo e seu uso ao trabalhar com PWAs, como citado na estrutura de arquivos.

Componentes

No React, trabalhamos sempre com componentes. Podemos dizer que tudo é um componente em React… Um botão, um grid com linhas e alguns botões, um input de texto, uma tela inteira é um componente que contém outros componentes.

Antes de exerecitarmos melhor como separar componentes, vamos ver a estrutura de um e iniciar a criação do layout de nossa aplicação, em um único componente, para definirmos a primera página de nossa aplicação e na terceira parte, faremos um exercício onde vamos refatorar a tela quebrando ela em componentes reutilizáveis.

A estrutura de um componente em React pode ser dividida em duas: Componentes Class e Stateless, sendo que:

  • Compoentes Class: São componentes mais complexos, que recebem propriedades e tem um controle interno de estado que pode ser modificado e gerar uma nova renderização do componente com informações dinâmicas. Ele será renderizado novamente, assim como seus filhos, após seu estado ser modificado e você pode ainda controlar o ciclo de vida dele.
  • Componentes Stateless: São componentes que não possuem estado, ou seja, você não pode acessar o “this.state” deles e também não possuem um ciclo de vida, não permitem controlar este ciclo, como os componentes Class. Basicamente são utilizados para componentes com alguma informação estática que não mudará até que o “pai” deste componente mude e se renderize novamente, afetando seus filhos.

Quando criamos um componente Class, criamos uma classe que extende a classe React.Component, fornecendo o ciclo de vida de um componente com estado. Este componente precisa obrigatóriamente ter implementado um método chamado render, que irá retornar um nó de elemento, como uma tag <div> por exemplo, para que o React renderize na tela. Um detalhe é que este método deve retornar apenas um nó raiz, ou teremos um erro na transpilação do código. Caso tenhamos dois ou mais elementos, eles devem ser envelopados em um elemento raiz. Abaixo temos um exemplo de código deste tipo de componente:

Código de exemplo de um component que recebe uma propriedade e armazena em seu estado interno.

Notem alguns detalhes, como:

  • No constructor, atribuímos diretamente um objeto para a propriedade this.state, que é o estado interno do componente. Apenas no construtor podemos fazer desta forma. Veremos isto mais adiante.
  • No método render, tenho duas tags <p>, mas estas estão envelopadas dentro de uma tag raiz, a <div>.
  • Na primeira tag <p>, estou exibindo na tela “Hello”, seguido do nome informado ao estado do componente pelas propriedades dele. Caso o conteúdo do estado seja modificado, esta tag <p> ser renderizada novamente com a informação atualizada no browser.

Ao utilizar este componente dentro de outro componente, como uma Página (lembre-se, página, controle, lista, tabela, estruturamos praticamente todos como componentes), basta importar ele e utilizar a tag com o nome dele e a propriedade Name, conforme o exemplo abaixo:

Componente sendo utilizado, com a propriedade Name sendo informada.

Notem que como estou renderizando apenas meu componente, não precisei de tags como a <div>, apenas retornei ele no método render(), e informei a propriedade Name como “John”. Isto vai renderizar no browser “Hello John”.

A sintaxe parece ser complexa quando vemos pela primeira vez, mas na realidade é bem simples, mais questão de se habituar ao seu uso.

Bom, vamos começar a criar o layout da listagem de tarefas de nossa aplicação, sem estilo ou funcionalidade neste momento, apenas para definirmos o rascunho da nossa aplicação.

Mão na massa!

Vamos aproveitar nossa aplicação criada na parte 1 e fazer uma limpeza nos arquivos antes de começar.

No diretório src da aplicação, apague os arquivos:

  • App.test.js
  • App.css
  • index.css
  • logo.svg
  • serviceWorker.js

No arquivo index.js, remova as linhas a seguir:

  • import ‘./index.css’;
  • import * as serviceWorker from ‘./serviceWorker’;
  • Comentários após o ReactDOM.render…
  • serviceWorker.unregister();

E no arquivo App.js, remova as linhas a seguir:

  • import logo from ‘./logo.svg’;
  • import ‘./App.css’;

E também, no método render, deixe seu conteúdo apenas conforme a imagem a seguir:

Arquivo App.js limpo e pronto para começarmos nosso aplicativo Todo List.

Com isso também nossa estrutura de arquivos estará desta forma:

Estrutura do projeto após removermos os arquivos desnecessários.

Vamos montar a tela principal da aplicação, ainda sem CSS, nem nada, e também apenas com as tags HTML que conhecemos, sem pensar, por enquanto, na criação de componentes e reuso destes… Mas para isso, precisamos inicialmente de alguns dados, fixos por enquanto, para poder validar a construção da tela.

Vamos colocar um método construtor no componente App, inicializando o estado dele com algumas informações, como no código a seguir.

Iniciando o estado do componente principal com alguns valores fixos para desenvolvermos o layout.

Basicamente definimos um construtor para o componente, que recebe um objeto contendo as propriedades passadas para este componente, mas não se preocupe agora com isso, veremos mais a frente. Chamamos o construtor pai através do método super e em seguida, iniciamos o estado do componente com um objeto, que contém uma propriedade “tasks”, que tem seu valor como um array de objetos contendo tarefas, com Id, Descrição e um flag indicando se foi concluída.

Um detalhe importante… O único lugar em que podemos definir o estado desta forma, fazendo um this.state = {}, é no construtor. Por que? Por que o estado de um componente é imutável. Para que possamos alterar o estado em algum evento / método de um componente, precisamos utilizar o método this.setState(), onde passamos um novo estado para substituir por completo o anterior.

Podemos resolver esta questão de duas formas:

  • Criamos uma cópia do estado, já com as mudanças e passamos para o setState. Notem que utilizamos o Object.assign para criar esta cópia, onde passamos como parâmetros o estado atual e um objeto para que ele faça o “merge” e retorne um novo objeto. Em seguida, o método setState se encarrega de substituir o estado do componente.
Object.Assign.
  • Utilizando o Spread Operator (se não conhece, recomendo ler este link da Mozilla), já informando o valor atualizado, como parâmetro do setState. O Spread Operator (… antes do this.state) faz com que o estado atual seja expandido dentro de um novo objeto, junto com a nossa nova propriedade teste. Em seguida, o setState faz o trabalho dele.
.

Além do fato do estado de um componente ser imutável, o setState se encarrega de notificar o Virtual Dom que algo naquele componente e seus filhos foi alterado e estes precisam ser renderizados novamente, ou não, dependendo da comparação entre a árvore de componentes virtual e a do browser.

Voltando ao nosso código, vamos agora criar uma lista dentro do elemento <div> que temos sendo renderizado no método render() do componente App. Mas, temos vários elementos no array de tasks do estado, e precisamos renderizar um abaixo do outro. Para isto temos o método map(), que podemos utilizar para percorrer um array e retornar algo a partir de uma função ou simplesmente um valor.

Ele é bem semelhante a um for…each utilizado em outras linguagens, mas retorna um valor para cada elemento do array. Para deixar mais claro, nosso código ficará assim:

Fazendo um loop para listar as tarefas contidas no estado do componente.

Notem que no meio das tags “html”, entre aspas pois estamos usando a sintaxe JSX, abrimos um trecho para código JS, entre as chaves { } onde acessamos a propriedade tasks do estado do componente, que é um array, e chamamos a função map. Este método recebe uma função, onde o primeiro parâmetro é um elemento do array, que será chamada para cada posição do mesmo. No código acima, usei uma arrow function, que é uma forma diferente de definir uma função. Como resultado da função, retornamos um elemento <li> com o valor da propriedade que contém a descrição da tarefa passada para a função naquele momento.

Notem que dentro do elemento <li> utilizamos novamente as chaves { } para acessar a propriedade description do objeto task. Isso é necessário pois estamos dentro do contexto da função, com um novo elemento JSX. Parece complicado, mas é mais uma questão de se adaptar a sintaxe.

O resultado deste map, que chamou a função para cada elemento do array, gerando por agora 3 elementos <li> é retornado e renderizado dentro do elemento <ul>. Se o projeto estiver em execução (caso não esteja, execute um npm start no diretório do projeto), já podemos ver os elementos aparecendo no browser:

Resultado da implementação do loop em execução no browser.

Se alterarmos qualquer elemento da coleção fixa no construtor e salvarmos o arquivo App.js, em poucos segundos temos o conteúdo atualizado no browser, graças ao hot reload.

Ainda sem funcionalidade, vamos criar alguns botões na frente de cada task, para Finalizar, Reabrir ou Excluir uma tarefa. Nosso código fica assim:

Adicionando botões de ação ao loop de tarefas.

Apenas quebrei algumas linhas e identei o código para ficar mais legível, mas apenas adicionei na frente da descrição 3 botões simples. O visual, ainda bem feio, pode ser conferido no browser:

Aplicação em execução no browser, ainda sem estilização.

Vamos adicionar um pouco de lógica a nossa aplicação. Para os botões Finalizar e Reativar, quero que sejam exibidos, respectivamente, quando a tarefa não estiver concluída ou quando estiver. Para isso, vamos colocar cada um dos botões dentro de uma seção com chaves { } e utilizar operador condicional para retornar o elemento ou simplesmente null, para que o React não renderize nada quando receber o null. Vamos alterar a tarefa com Id 2, no construtor, para que a propriedade done seja true. Assim podemos validar a renderização de ambos os botões. Nosso código fica desta forma:

Adicionando lógica para exibição dos botões de ação de acordo com o status da tarefa.

Se task.done for falso, o Finalizar é renderizado o Reativar não. Se task.done for verdadeiro, Finalizar não é renderizado e o Reativar sim. Podemos ver o resultado no browser, inclusive com ambos os casos, pois temos as tasks 1 e 3 como não finalizadas e a 2 como finalizada.

Vamos agora criar duas funções em nosso componente para finalizar e reativar as tarefas. Estas funções devem receber o Id da tarefa para poderem realizar a alteração. Ambas irão utilizar outra função para buscar a tarefa desejada e realizar a alteração. Assim separamos bem a responsabilidade de cada uma delas, seguindo um pouco do paradigma de programação funcional. Vejamos como fica nosso código e em seguida vamos explicar melhor:

Criamos uma função chamada modifyTaskStatus, que recebe dois argumentos: Id da tarefa e status da mesma (boolean). Dentro dela criamos uma cópia do estado atual, através do Object.assign, localizamos a tarefa desejada nesta cópia do estado, modificamos a propriedade done dela conforme o segundo argumento da função e em seguida, fazemos uma chamada ao this.setState para que configure o estado de nosso componente com o objeto clonado no início da função.

Os outros dois métodos, completeTask e resetTask, configuram a propriedade done da tarefa para true ou false, respectivamente, chamando a função modifyTaskStatus informando o id da tarefa e o estado desejado para ela.

Notem que nos botões, para poder passar o Id da tarefa como argumento para as funções, tive que implementar uma arrow function para poder tratar o evento OnClick e chamar a função dentro dele. Se eu colocasse simplesmente o this.completeTask no evento onClick, eu apenas poderia receber o argumento do objeto que disparou o evento click. Veremos isto em outro exemplo futuramente.

Neste momento, nossa aplicação já “funciona”. Ao clicar em reativar, podemos notar que os botões já são renderizados automaticamente após o estado ser modificado.

Exemplo funcionando, com a modificação do estado, concluíndo e reativando as tarefas.

Para finalizar esta parte da série, vamos implementar uma função para remover uma tarefa da lista. Como esta função tem apenas esta responsabilidade, já vamos aplicar o estado no componente. Nosso código fica desta forma:

Função resetTask e sua chamada pelo botão Remover.

Basicamente na função removeTask, recebemos o Id da tarefa. Com isso, criamos uma cópia do estado atual, substituímos a lista de tarefas por uma nova lista utilizando um filter, onde filtramos as tarefas com Id diferente da tarefa informada, assim “removemos” ela e aplicamos novamente ao estado.

No evento onClick do botão Remover, novamente implementei uma arrow function para poder tratar o evento e fiz a chamada para a função removeTask.

Podemos ver agora a funcionalidade de remover tarefa funcionando.

Funcionalidade de remover tarefa em execução.

Concluíndo

Como o artigo já está um pouco extenso, vamos continuar na próxima parte e finalizar com a adição e alteração de tarefas, assim não vamos mais precisar do estado inicial com as tarefas de testes, pois toda vez que atualizarmos a página do navegador, as tarefas de testes serão recarregadas.

Vamos também persistir o estado delas através do storage local do navegador.

Caso tenham dúvidas ou mesmo feedbacks sobre melhorias e correções na forma que estou apresentando o conteúdo, por favor, fiquem a vontade para entrar em contato. Com o feedback de vocês, posso melhorar cada vez mais o conteúdo que produzo para todos!

Um abraço e até a próxima parte.