Objeto: Propriedades de Componentes e Desestruturação
Principais termos técnicos abordados: JSX, React, Desestruturação ReactDOM, JavaScript, HTML
Requisitos para as instruções funcionarem: ter cumprido as Receitas anteriores e trabalhar com um arquivo html dentro do mesmo diretório criados nelas
Requisitos para compreensão das instruções: noções bem básicas de programação JavaScript e de HTML
Como ler essa receita: instruções, dentro dos passos da receita e que requerem ação sua no computador, estarão escritas em cor azul. Comentários sobre os passos estarão em fonte normal, cor preta. Comandos, código-fonte, termos técnicos ou configuração explícita, estarão com fonte diferenciada.
Observação: eventuais instruções de terminal serão comandos Linux. Adapte caso esteja usando outro sistema operacional
Vamos partir de um componente React personalizado TabelaBebidas, escrito na forma de função JavaScript em receita anterior.
Salve um arquivo html com o seguinte conteúdo (use o mesmo diretório criado nas receitas anteriores).
<!DOCTYPE html>
<html>
<head>
<title>ReceitaReact - 3</title>
</head>
<body>
<div id="app">
</div>
<script src="libs/react.js"/>
<script src="libs/react-dom.js"/>
<script src="libs/babel.js"/>
<script type="text/babel">
const TabelaBebidas = () => {
return (
<div>
<h1>Tabela de Bebidas</h1>
<table id="tabela" border="1">
<thead>
<tr><th>Nome</th><th>Marca</th></tr>
</thead>
<tbody>
<tr><td>Havana</td><td>Engenho Anísio Santiago</td></tr>
</tbody>
</table>
</div>
)
}
ReactDOM.render(
<TabelaBebidas/>
,
document.getElementById("app"))
</script>
</body>
</html>
Suponha agora que queiramos parametrizar nosso componente, nossa marca <TabelaBebidas/>. Note as marcas <script> acima. Passamos informações para elas e damos nomes e valores às informações que passamos. Não precisa ser muito perspicaz para inferir que em <script src="libs/babel.js"/> estamos passando o valor "libs/babel.js" sob a identificação de src. Nada mais parecido com parâmetro de função do que isso mas, no mundo das marcas, a gente chama esses valores de propriedades. Propriedades são, assim, valores associados a identificadores dentro da marca de abertura. Não confundi-las JAMAIS com o conteúdo que se encontra entre as marcas de abertura e fechamento. A marca...
<table id="tabela" border="1">
<thead>
<tr><th>Nome</th><th>Marca</th></tr>
</thead>
<tbody>
<tr><td>Havana</td><td>Engenho Anísio Santiago</td></tr>
</tbody>
</table>
... tem duas propriedades e dois filhos, ou nodos. As duas propriedades são id e border, que estão devidamente declaradas dentro da marca de abertura. Os filhos são as marcas <thead> e <tbody>.
- Ótimo. Então se a gente quiser passar uma propriedade titulo para nossa marca <TabelaBebidas/> bastaria reescrevê-la <TabelaBebidas titulo="Minha tabela de bebidas preferidas"/> ???
Bastaria.
- Ah, professor, mas eu fiz isso, salvei, e não aconteceu nada...
É porque você apenas escreveu a propriedade na marca. Na prática isso significa que esse valor vai ser passado como parâmetro para sua função TabelaBebidas, que é a implementação JS da sua marca <TabelaBebidas>. Falta ensinar a essa função a usar esse parâmetro que ela vai receber. Você precisa declarar o parâmetro na função e usá-lo entre chaves se quiser acessá-lo no código JSX...
- Ah, já sei. Então é só eu declarar um parâmetro titulo na função e empurrar ele entre chaves dentro da marca <h1>...
Vai lá, jovem padawan. Faça isso.
Modifique a função TabelaBebidas, salve arquivo, abra no navegador, inspecione:
const TabelaBebidas = (titulo) => {
return (
<div>
<h1>{titulo}</h1>
<table id="tabela" border="1">
<thead>
<tr><th>Nome</th><th>Marca</th></tr>
</thead>
<tbody>
<tr><td>Havana</td><td>Engenho Anísio Santiago</td></tr>
</tbody>
</table>
</div>
)
}
- Ah, professor, deu esse erro aqui, ó: Uncaught Error: Objects are not valid as a React child (found: object with keys {titulo}). If you meant to render a collection of children, use an array instead.
Pois é jovem, o JavaScript, não sabe quantas e quais propriedades seu juízo de empinador de pipa em ventilador vai fazer você escrever na sua marca <TabelaBebidas>. Então, todas as propriedades que você escrever o JavaScript vai encapsular num objeto só e passar para a função (o React não aceita objeto como filho de marcas). Esse objeto terá como atributos todas as propriedades que você utilizar na marca. Ao declarar a função assim...
const TabelaBebidas = (titulo) => { ... }
...você está dando o nome de titulo a um objeto que, na verdade, vai ter DENTRO dele um atributo titulo. Então a gente começa dando um nome mais idôneo para o parâmetro da função...
const TabelaBebidas = (props) => { ... }
... e na hora de acessar a gente modifica para a expressão entre chaves acessar o atributo titulo de props...
<h1>{props.titulo}</h1>
Desta forma, modifique o código da página para que fique como a seguir:
<!DOCTYPE html>
<html>
<head>
<title>ReceitaReact - 3</title>
</head>
<body>
<div id="app">
</div>
<script src="libs/react.js"></script>
<script src="libs/react-dom.js"></script>
<script src="libs/babel.js"></script>
<script type="text/babel">
const TabelaBebidas = (props) => {
return (
<div>
<h1>{props.titulo}</h1>
<table id="tabela" border="1">
<thead>
<tr><th>Nome</th><th>Marca</th></tr>
</thead>
<tbody>
<tr><td>Havana</td><td>Engenho Anísio Santiago</td></tr>
</tbody>
</table>
</div>
)
}
ReactDOM.render(
<TabelaBebidas titulo="minha tabela de cana"/>
,
document.getElementById("app"))
</script>
</body>
</html>
- Gostei não, professor. Tiver uma ruma de atributo eu vou ter que ficar fazendo props.isso, props.aquilo, props.aquiloOutro.
Não necessariamente. As versões mais novas do JavaScript têm um negócio chamado destructuring. Uma tradução provavelmente aceita poderia ser desestruturação. Em bom nordestinês, poderia ser "arregaçar". A gente pode arregaçar esse objeto passado como parâmetro para a função de forma a poder usar os nomes dos atributos diretamente, sem props.isso ou props.aquilo.
É mais fácil de fazer isso do que a explicação dá a entender. Se a gente declarar a função assim...
const TabelaBebidas = ({titulo}) => {...}
...estamos indicando que a função recebe um objeto que tem um de seus atributos identificado por titulo. NOTE as chaves, elas fazem TODA a diferença. São ELAS que estão indicando isso!
Nesse caso, COM O USO DAS CHAVES, neste parâmetro titulo será colocado o valor da propriedade titulo definida na marca <TabelaBebidas titulo="minha tabela de cana"/>. Se você passar outras propriedades, elas estarão lá no objeto que foi passado como parâmetro, apenas seu código não vai fazer nada com elas. O que acontece nessa tal desestruturação? Informalmente, o objeto passado como parâmetro é "arregaçado", procura-se alguma propriedade titulo lá e, em ela sendo encontrada, seu valor é atribuído diretamente ao atributo titulo. Que é um string e pode ser usado diretamente...
<h1>{titulo}</h1>
E morreu Maria Preá.
- Mas e se eu quisesse usar outras propriedades, professor?
Empurre uma vírgula e use quantas quiser.
Na marca, escreva...
<TabelaBebidas titulo="minha tabela de cana" subtitulo="só as melhores"/>
No cabeçalho da função, escreva...
const TabelaBebidas = ({titulo, subtitulo}) => {...}
E no JSX retornado pela função, escreva o que quiser, mas pode ser algo como...
<h1>{titulo}</h1>
<h2>{subtitulo}</h2>
Perceba que um mesmo recurso sintático, abraçar uma variável com chaves, foi usado para expressar duas coisas completamente diferentes.
Usamos {titulo} na listagem de parâmetros da função: const TabelaBebidas = ({titulo}) => {...}
Usamos {titulo} dentro de marcas: <h1>{titulo}</h1>
Apenas o primeiro caso se trata de desestruturação.
No segundo caso, as chaves servem tão somente para montar marcas com valores que estão em variáveis JavaScript. Não tem nada a ver com desestruturação. É uma espécie de injeção de valores numa expressão literal e, funciona, nesse caso, parecido com a injeção de valores que podemos fazer com os novos strings JavaScript, aqueles com o sinal grave. `O titulo é ${titulo}` iria injetar o valor que consta na variável titulo dentro do string que consta entre os acentos graves.
- Ah, professor, e porque nos strings tem que usar o dólar e no JSX <h1>{titulo}</h1> não tem?
Basicamente, porque os caras que inventaram o negócio assim quiseram.
Há outras formas de desestruturar objetos JavaScript, dê uma pesquisada para saber mais.
- Mas dá pra usar a notação com ponto e esquecer a desestruturação?
Dá. Mas você vai ter dificuldade em entender muito código escrito aí pela Internet e vai ter mais dificuldade de trabalhar em equipe com programadores mais dispostos que você. Então, na verdade, não dá. Dê uma chance à desestruturação e você haverá de apaixonar-se por ela.
- Tem mais alguma notícia ruim, professor?
Na verdade, meio que tem sim. Vamos lá.
No início usamos as {} em <h1>{titulo}</h1>, antes de desestruturar o parâmetro, quando {titulo} denotava um objeto estruturado e não um valor de string.
O inspetor de código do navegador gentilmente nos explicou que Objetos [estruturados, estilo JSON] não são válidos como um nó React ("Objects are not valid as a React child"). Isso porque estávamos escrevendo a expressão onde escreveríamos um nó, dentro de uma marca, no caso, <h1>. No entanto, no React, podemos usar objetos estruturados como propriedades para as marcas, em especial para as marcas que são componentes que nós próprios definimos, e isso é bem útil.
Que tal passarmos uma propriedade para a marca <TabelaBebidas> que seja um objeto estruturado com atributos cabecalho1 e cabecalho2?
Modifique o conteúdo de sua última marca <script> para que fique como a seguir:
const TabelaBebidas = ({titulo,cabecalhos}) => {
return (
<div>
<h1>{titulo}</h1>
<table id="tabela" border="1">
<thead>
<tr><th>{cabecalhos.cabecalho1}</th><th>{cabecalhos.cabecalho2}</th></tr>
</thead>
<tbody>
<tr><td>Havana</td><td>Engenho Anísio Santiago</td></tr>
</tbody>
</table>
</div>
)
}
const valoresCabecalhos = {cabecalho1: "Nome", cabecalho2: "Marca"}
ReactDOM.render(
<TabelaBebidas titulo="minha tabela de cana" cabecalhos={ valoresCabecalhos }/>
,
document.getElementById("app"))
- Legal, mas não daria para desestruturar o cabeçalho para evitar o uso de objeto.isso/objeto.aquilo e o código ficar mais legível??
Dá sim. Vamos lá.
Modifique o conteúdo de sua última marca <script> para que fique como a seguir:
const TabelaBebidas = ({titulo,cabecalhos:{cabecalho1,cabecalho2} }) => {
return (
<div>
<h1>{titulo}</h1>
<table id="tabela" border="1">
<thead>
<tr><th>{cabecalho1}</th><th>{cabecalho2}</th></tr>
</thead>
<tbody>
<tr><td>Havana</td><td>Engenho Anísio Santiago</td></tr>
</tbody>
</table>
</div>
)
}
const valoresCabecalhos = {cabecalho1: "Nome", cabecalho2: "Marca"}
ReactDOM.render(
<TabelaBebidas titulo="minha tabela de cana" cabecalhos={ valoresCabecalhos }/>
,
document.getElementById("app"))
- Né por nada não, mas nessa propriedade cabecalhos seria bem melhor um array de string...
Seria mesmo. Bora...
Modifique o conteúdo de sua última marca <script> para que fique como a seguir:
const TabelaBebidas = ({titulo,cabecalhos:[cabecalho1,cabecalho2] }) => {
return (
<div>
<h1>{titulo}</h1>
<table id="tabela" border="1">
<thead>
<tr><th>{cabecalho1}</th><th>{cabecalho2}</th></tr>
</thead>
<tbody>
<tr><td>Havana</td><td>Engenho Anísio Santiago</td></tr>
</tbody>
</table>
</div>
)
}
const valoresCabecalhos = ["Nome", "Marca"]
ReactDOM.render(
<TabelaBebidas titulo="minha tabela de cana" cabecalhos={ valoresCabecalhos }/>
,
document.getElementById("app"))
- Vige, então eu posso desestruturar arrays também???
E Viva Santana...
- Mas o que acontece se eu passar mais de dois elementos na propriedade cabecalhos, lá na chamada da marca <TablaBebidas>???
Seu código tá desestruturando apenas os dois primeiros elementos e fazendo alguma coisa apenas com os dois primeiros elementos, então se você pode passar o vetor com mais de 2 elementos não vai dar erro mas também não vai fazer diferença na composição da interface gráfica.
- E tem como eu tornar isso flexível??? Tipo, se eu passar um vetor de 3 na propriedade cabecalhos, ele monta 3 colunas no cabeçalho, e assim por diante?
Tem sim, bem-vindo aos exercícios.
Apague o corpo de sua tabela e concentre-se apenas no cabeçalho. Faça com que seu componente monte uma tabela com todos os valores do array cabecalhos.
Agora volte a escrever o corpo de sua tabela. Desta feita, sua marca deve passar como propriedade também o conteúdo da tabela (além do cabeçalho). E seu componente deve montar o conteúdo de acordo com essa propriedade. Note que pode ser mais de 2 colunas no conteúdo, assim como no cabeçalho, e o conteúdo pode ter diversas linhas também
Bom vídeo de 100 segundos (em Inglês), sobre destructuring.
Página em português, desestruturação.
Página em inglês, desestruturação
Javascript destructuring, react component props