Pular para o conteúdo

a

Renderização de uma coleção e módulos

Antes de começar esta nova parte, vamos revisar alguns dos tópicos que, ano passado, se provaram difíceis para alguns estudantes.

console.log

* Qual é a diferença entre um programador de JavaScript experiente e um iniciante? O experiente usa o console.log 10, 100 vezes mais. *

Paradoxalmente, isso parece ser verdade, mesmo que um programador iniciante precise do console.log (ou de qualquer outro método de depuração) mais do que um experiente.

Quando algo não funciona, não tente adivinhar o que está errado. Em vez disso, faça o log ou use outra forma de depuração.

Obs.: Como explicado na Parte 1, ao usar o comando console.log para depuração, não concatene coisas "do jeito Java" com o sinal de adição (+). Em vez de escrever

console.log('valor de props é ' + props)

separe os valores a serem impressos com uma vírgula:

console.log('valor de props é', props)

Se você concatenar um objeto com uma string e fazer o registro (log) no console (como demonstrado em nosso primeiro exemplo), o resultado será bem inútil:

valor de props é [object Object]

Pelo contrário, quando você passa objetos como argumentos distintos separados por vírgulas para o console.log, como no nosso segundo exemplo acima, o conteúdo do objeto é impresso no console do desenvolvedor como strings que são informativas. Se necessário, leia mais sobre depuração de aplicações React.

Dica: Atalhos (Snippets) do Visual Studio Code

Com o Visual Studio Code, é fácil criar "snippets", ou seja, "atalhos" para gerar rapidamente porções de código que são reutilizadas diversas vezes, assim como o "sout" no Netbeans.

As instruções para criar atalhos podem ser encontradas aqui.

Atalhos úteis pré-prontos também podem ser encontrados como plugins do VS Code, no marketplace.

O atalho mais importante é o do comando console.log(), por exemplo, clog. Ele pode ser criado assim:

{
  "console.log": {
    "prefix": "clog",
    "body": [
      "console.log('$1')",
    ],
    "description": "Registra saída no console"
  }
}

Depurar seu código usando console.log() é algo tão trivial que o Visual Studio Code já tem esse atalho embutido. Para usá-lo, digite log e aperte Tab para autocompletar. Extensões do atalho console.log() mais completas podem ser encontradas no marketplace.

Arrays em JavaScript

A partir daqui, usaremos os operadores de programação funcional do array (matriz) JavaScript, como find, filter e map o tempo todo.

Se operar arrays com operadores funcionais parecer estranho para você, vale a pena assistir pelo menos os primeiros três vídeos da série Programação Funcional em JavaScript no YouTube:

Revisão sobre Gerenciadores de Evento

Baseado no curso do ano passado, o gerenciamento de eventos provou ser algo difícil.

Vale a pena ler o capítulo de revisão no final da parte anterior — Revisão sobre Gerenciamento de Eventos — caso ainda ache que precise estudar mais sobre o assunto.

A passagem de gerenciadores de eventos para os componentes-filho do componente App levantou algumas questões. Uma pequena revisão sobre o tópico pode ser encontrada aqui.

Renderização de Coleções

Nota dos tradutores: A partir deste momento, os códigos utilizados como exemplo permanecerão no idioma original (inglês), visto que é disponibilizado ao final de cada sessão o repositório onde o código-exemplo pode ser encontrado na íntegra. É muito provável que o estudante se confunda caso os nomes de variáveis, funções, componentes, etc estejam em português, dado que estaria diferente do código disponibilizado no repositório do GitHub, que está em inglês.

Faremos neste momento a lógica da aplicação do lado do cliente (navegador), ou o "front-end", em React, para uma aplicação semelhante à aplicação de exemplo da Parte 0.

Comecemos com o seguinte (arquivo App.js):

const App = (props) => {
  const { notes } = props

  return (
    <div>
      <h1>Notes</h1>
      <ul>
        <li>{notes[0].content}</li>
        <li>{notes[1].content}</li>
        <li>{notes[2].content}</li>
      </ul>
    </div>
  )
}

export default App

O arquivo index.js fica assim:

import React from 'react'
import ReactDOM from 'react-dom/client'

import App from './App'

const notes = [


  {
    id: 1,
    content: 'HTML é fácil',
    important: true
  },
  {
    id: 2,
    content: 'O navegador só pode executar JavaScript',
    important: false
  },
  {
    id: 3,
    content: 'GET e POST são os métodos mais importantes do protocolo HTTP',
    important: true
  }
]

ReactDOM.createRoot(document.getElementById('root')).render(
  <App notes={notes} />
)

Cada nota contém seu conteúdo textual, um valor booleano para marcar se a nota foi categorizada como importante ou não e também um id (identificador) único.

O exemplo acima funciona devido ao fato de haver exatamente três notas no array.

Uma única nota é renderizada acessando os objetos no array, referindo-se a um número de índice no "código de teste":

<li>{notes[1].content}</li>

Isso, obviamente, não é algo prático. Podemos melhorar nosso código gerando elementos React a partir dos objetos do array usando a função map (mapear).

notes.map(note => <li>{note.content}</li>)

O resultado é um array de elementos li...

[
  <li>HTML é fácil</li>,
  <li>O navegador só pode executar JavaScript</li>,
  <li>GET e POST são os métodos mais importantes do protocolo HTTP</li>,
]

... que então podem ser colocados dentro de tags ul:

const App = (props) => {
  const { notes } = props

  return (
    <div>
      <h1>Notes</h1>
      <ul>        {notes.map(note => <li>{note.content}</li>)}      </ul>    </div>
  )
}

Como o código que gera as tags li é JavaScript, ele deve ser envolto em chaves no modelo JSX, assim como todo código JavaScript.

Também faremos com que o código fique mais legível separando a declaração da função de seta em várias linhas:

const App = (props) => {
  const { notes } = props

  return (
    <div>
      <h1>Notes</h1>
      <ul>
        {notes.map(note => 
          <li>            {note.content}          </li>        )}
      </ul>
    </div>
  )
}

O atributo "key" (chave)

Mesmo que a aplicação pareça estar funcionando, há um aviso no console:

erro da propriedade de chave única no console

Como sugere a página React vinculada na mensagem de erro, os itens da lista, ou seja, os elementos gerados pelo método map, devem ter cada qual um valor único que os permite serem identificados: um atributo chamado key (chave).

Vamos adicionar as keys:

const App = (props) => {
  const { notes } = props

  return (
    <div>
      <h1>Notes</h1>
      <ul>
        {notes.map(note => 
          <li key={note.id}>            {note.content}
          </li>
        )}
      </ul>
    </div>
  )
}

E então, a mensagem de erro desaparece.

React usa os atributos "key" (ou atributos-chave) dos objetos em um array para determinar como atualizar a visualização gerada por um componente quando o componente é re-renderizado. Leia mais sobre esse assunto na documentação React.

Map

Entender como funciona o método de array map é crucial para fazer o restante do curso.

A aplicação contém um array chamado notes:

const notes = [
  {
    id: 1,
    content: 'HTML is easy',
    important: true
  },
  {
    id: 2,
    content: 'Browser can execute only JavaScript',
    important: false
  },
  {
    id: 3,
    content: 'GET and POST are the most important methods of HTTP protocol',
    important: true
  }
]

Vamos parar por um momento e examinar como o map funciona.

Se o código a seguir for adicionado, digamos, ao final do arquivo:

const result = notes.map(note => note.id)
console.log(result)

[1, 2, 3] será impresso no console. O método map sempre cria um array novo, cujos elementos foram criados a partir dos elementos do array original por meio do mapping (mapeamento): usa-se a função fornecida como um parâmetro para o método map.

A função é esta:

note => note.id

Que, neste caso, é uma arrow function escrita de forma compacta. A forma completa seria:

(note) => {
  return note.id
}

A função recebe um objeto "note" como parâmetro e retorna o valor de seu campo id.

Se mudarmos a instrução para:

const result = notes.map(note => note.content)

o resultado será um array contendo as notas.

Essa forma está bem parecida com o código React que usamos:

notes.map(note =>
  <li key={note.id}>
    {note.content}
  </li>
)

o qual gera uma tag li contendo o conteúdo da nota de cada objeto de nota.

Por conta do parâmetro da função passado para o método map

note => <li key={note.id}>{note.content}</li>

 — ser usado para criar elementos de visualização, o valor da variável deve ser renderizado dentro de chaves. Tente ver o que acontece se as chaves forem removidas.

O uso constante de chaves pode gerar algum desconforto no início, mas você se acostumará rapidamente com elas. O feedback visual em React é imediato.

Antipadrão: Índices de Array como Keys

Poderíamos ter feito a mensagem de erro em nosso console desaparecer usando os índices do array como keys. Os índices podem ser recuperados passando um segundo parâmetro para a função de retorno do método map:

notes.map((note, i) => ...)

Quando chamado desta forma, é atribuído ao i o valor do índice da posição no array onde a nota reside.

Como tal, uma forma de definir a criação de linhas (row) sem gerar erros é esta:

<ul>
  {notes.map((note, i) => 
    <li key={i}>
      {note.content}
    </li>
  )}
</ul>

Entretanto, isso não é recomendado visto que pode criar problemas indesejados mesmo se parecer estar funcionando bem.

Leia mais sobre isso neste artigo.

Refatorando módulos

Vamos arrumar um pouco nosso código. Estamos interessados apenas no campo notes das props, então vamos recuperá-lo diretamente usando desestruturação:

const App = ({ notes }) => {  return (
    <div>
      <h1>Notes</h1>
      <ul>
        {notes.map(note => 
          <li key={note.id}>
            {note.content}
          </li>
        )}
      </ul>
    </div>
  )
}

Se você esqueceu o que significa desestruturação e como essa ferramenta funciona, por favor, revise a seção sobre desestruturação.

Vamos separar a exibição de uma única nota em seu próprio componente Note:

const Note = ({ note }) => {  return (    <li>{note.content}</li>  )}
const App = ({ notes }) => {
  return (
    <div>
      <h1>Notes</h1>
      <ul>
        {notes.map(note =>           <Note key={note.id} note={note} />        )}      </ul>
    </div>
  )
}

Note que o atributo key agora deve ser definido para os componentes Note, e não para as tags li como antes.

Uma aplicação React pode ser escrita inteiramente em um único arquivo, embora fazer isso não seja muito prático. O ideal é declarar cada componente em seu próprio arquivo como um módulo ES6.

Estamos utilizando módulos o tempo todo. As primeiras linhas do arquivo index.js:

import ReactDOM from "react-dom/client"

import App from "./App"

importam dois módulos, habilitando-os a serem usados ​​nessa pasta. É importado o módulo react-dom/client para a variável ReactDOM e o módulo que define o componente principal da aplicação é atribuído à variável App.

Vamos separar nosso componente Note em um módulo próprio.

Em aplicações menores, os componentes geralmente são colocados em uma pasta chamada components, que por sua vez é colocada dentro da pasta src. A convenção é nomear o arquivo com o nome do componente.

Agora, criaremos uma pasta chamada components para nossa aplicação e criaremos dentro dela um arquivo chamado Note.js. O conteúdo do arquivo Note.js é o seguinte:

const Note = ({ note }) => {
  return (
    <li>{note.content}</li>
  )
}

export default Note

A última linha do código exporta o módulo declarado, a variável Note.

Agora, o arquivo que está usando o componente — App.js — pode importar o módulo:

import Note from './components/Note'
const App = ({ notes }) => {
  // ...
}

O componente exportado pelo módulo passa a ficar disponível para uso na variável Note, assim como antes.

Observe que ao importar nossos próprios componentes, sua localização deve ser dada em relação ao arquivo importador:

'./components/Note'

O ponto — . — no começo se refere ao diretório atual, então a localização do módulo é um arquivo chamado Note.js no subdiretório de componentes do diretório atual. A extensão de arquivo .js pode ser omitida.

Módulos têm muitas outras utilidades além de permitir que as declarações de componentes sejam separadas em suas próprias instâncias. Voltaremos a falar sobre eles mais tarde neste curso.

O código atual da aplicação pode ser encontrado neste repositório GitHub.

Note que a branch main do repositório contém o código para uma versão posterior da aplicação. O código atual está na branch part2-1:

captura de tela da branch do GitHub

Caso deseje clonar o projeto, execute o comando npm install antes de iniciar a aplicação com npm run dev.

Quando a Aplicação Quebra

Logo cedo em sua carreira em programação (e até mesmo após 30 anos de programação como esta pessoa que vos escreve), o que acontece com frequência é que a aplicação simplesmente quebra, completamente. Isso é ainda mais verídico quando se trata de linguagens dinamicamente tipadas, como JavaScript, onde o compilador não verifica o tipo do dado declarado. Por exemplo, variáveis de função ou valores de retorno.

Uma "explosão React" pode parecer assim, por exemplo:

exemplo de erro em React

Nessas situações, sua melhor saída é utilizar o método console.log.

O pedaço de código que está causando a explosão é este:

const Course = ({ course }) => (
  // "Course" traduz-se como "Curso"
  // "Header" traduz-se como "Cabeçalho"
  <div>
    <Header course={course} />
  </div>
)

const App = () => {
  const course = {
    // ...
  }

  return (
    <div>
      <Course course={course} />
    </div>
  )
}

Vamos nos aprofundar investigando a razão do problema adicionando algumas linhas de console.log ao código. Por conta do componente App ser a primeira entidade a ser renderizada, vale a pena colocar o primeiro console.log lá:

const App = () => {
  const course = {
    // ...
  }

  console.log('App works...')
  return (
    // ..
  )
}

Para ver a impressão no console, devemos rolar por toda a longa parede vermelha de erros até chegar lá em cima.

impressão inicial do console

Quando se encontra alguma parte do código que está funcionando, é o momento exato para se aprofundar na impressão. Fica mais difícil de se imprimir no console se o componente foi declarado como uma única instrução ou uma função que não retorna nada.

const Course = ({ course }) => (
  <div>
    <Header course={course} />
  </div>
)

O componente deve ser alterado para sua forma mais extensa, no qual poderemos adicionar a impressão desejada:

const Course = ({ course }) => { 
  console.log(course)  return (
    <div>
      <Header course={course} />
    </div>
  )
}

Muitas vezes, a raiz do problema é que espera-se que as propriedades sejam de um tipo diferente, ou que sejam chamadas com um nome diferente do que realmente são, e a desestruturação falha como resultado. O problema começa a revelar-se quando a desestruturação é removida e vemos o que as props armazenam.

const Course = (props) => {  console.log(props)  const { course } = props
  return (
    <div>
      <Header course={course} />
    </div>
  )
}

Se o problema ainda não foi resolvido, infelizmente não há muito o que fazer, a não ser continuar a busca por erros adicionando mais comandos console.log ao seu código.

Adicionei este capítulo ao material após a resposta do modelo da próxima pergunta explodir completamente (devido as props estarem armazenando o tipo errado de dados), e precisei depurá-lo usando console.log.

Juramento do Programador Web

Antes de fazer os exercícios, deixe-me lembrá-lo do que havia jurado no final da parte anterior.

Programar é difícil, e é por isso que eu usarei todos os meios possíveis para ser mais fácil:

  • Eu manterei meu Console do navegador aberto o tempo todo;
  • Eu vou progredir aos poucos, passo a passo;
  • Eu escreverei muitas instruções console.log para ter certeza de que estou entendendo como o código se comporta e para me ajudar a identificar os erros;
  • Se meu código não funcionar, não escreverei mais nenhuma linha no código. Em vez disso, começarei a excluir o código até que funcione ou retornarei ao estado em que tudo ainda estava funcionando; e
  • Quando eu pedir ajuda no canal do Discord do curso ou em outro lugar, formularei minhas perguntas de forma adequada. Veja aqui como pedir ajuda.