Tutorial

Como criar componentes de encapsulamento no React com Props

DevelopmentJavaScriptReact

O autor selecionou a Creative Commons para receber uma doação como parte do programa Write for DOnations.

Introdução

Neste tutorial, você criará componentes de encapsulamento com props usando a biblioteca React JavaScript. Componentes de encapsulamento são componentes que encapsulam componentes desconhecidos e fornecem uma estrutura padrão para exibir os componentes filhos. Este padrão é útil para criar elementos de interface de usuário (UI) que são usados repetidamente ao longo de um projeto, como modais, páginas de modelo e contos informacionais.

Para criar componentes de encapsulamento, você primeiro aprenderá a usar os operadores rest e spread para coletar as props não utilizadas para passar aos componentes aninhados. Em seguida, você criará um componente que utiliza o componente embutido children para envolver componentes aninhados no JSX como se eles fossem elementos HTML. Por fim, você passará componentes como props para criar encapsuladores flexíveis que podem incorporar JSX personalizados em várias localizações em um componente.

Durante o tutorial, você compilará componentes para exibir uma lista de dados de animais na forma de cartões. Você aprenderá a dividir dados e refatorar componentes à medida que você cria componentes de encapsulamento flexíveis. Ao final deste tutorial, você terá uma aplicação funcional que usará técnicas avançadas de prop para criar componentes reutilizáveis que irão escalar e se adaptar à medida que sua aplicação cresce e se modifica.

Nota: o primeiro passo define um projeto em branco no qual você desenvolverá o exercício do tutorial. Se você já tiver um projeto de trabalho e deseja ir diretamente ao trabalho com os props, vá para o Passo 2.

Pré-requisitos

Passo 1 — Criando um projeto vazio

Neste passo, você criará um novo projeto utilizando o Create React App. Em seguida, você excluirá o projeto de exemplo e os arquivos relacionados que foram instalados durante a inicialização dele. Por fim, você criará uma estrutura de arquivos simples para organizar seus componentes. Isso lhe dará uma base sólida sobre a qual construir a aplicação de encapsulamento deste tutorial no próximo passo.

Para começar, crie um novo projeto. Em sua linha de comando, execute o script a seguir para instalar um novo projeto usando o create-react-app:

  • npx create-react-app wrapper-tutorial

Após o projeto ser finalizado, vá para o diretório:

  • cd wrapper-tutorial

Em uma nova guia ou janela do terminal, inicie o projeto usando o script start do Create React App. O navegador irá atualizar-se automaticamente nas alterações, então deixe este script em execução enquanto você trabalha:

  • npm start

Você receberá um servidor local em execução. Se o projeto não abriu na janela de um navegador, você pode abri-lo com http://localhost:3000/. Se o estiver executando em um servidor remoto, o endereço será http://your_domain:3000.

Seu navegador carregará com um aplicativo simples do React, que vem incluído como parte do Create React App:

Projeto modelo do React

Você irá desenvolver um conjunto completamente novo de componentes personalizados. Assim, será necessário começar removendo um pouco de código boilerplate para que você possa ter um projeto vazio.

Para iniciar, abra o src/App.js em um editor de texto. Esse é o componente raiz que é injetado na página. Todos os componentes iniciarão a partir daqui. Saiba mais sobre o App.js em Como configurar um projeto React com o Create React App.

Abra o src/App.js com o seguinte comando:

  • nano src/App.js

Você verá um arquivo como este:

wrapper-tutorial/src/App.js
import React from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

Exclua a linha import logo from './logo.svg';. Em seguida, substitua tudo na instrução return para retornar um conjunto de tags vazias: <></>. Isso lhe dará uma página válida que não retorna nada. O código final ficará parecido com este:

wrapper-tutorial/src/App.js

import React from 'react';
import './App.css';

function App() {
  return <></>;
}

export default App;

Salve e saia do editor de texto.

Por fim, exclua o logo. Você não o usará neste seu aplicativo e é aconselhável remover arquivos não utilizados durante o trabalho. Isso evitará confusões futuras.

Na janela do terminal, digite o seguinte comando:

  • rm src/logo.svg

Se você olhar em seu navegador, verá uma tela vazia.

tela vazia no chrome

Com o projeto de exemplo do Create React App limpo, crie uma estrutura de arquivos simples. Isso ajudará você a manter os componentes isolados e independentes.

Crie um diretório chamado components no diretório src. Isso irá conter todos os seus componentes personalizados.

  • mkdir src/components

Cada componente terá o próprio diretório para armazenar o arquivo de componente com os estilos, imagens (se houver) e testes.

Crie um diretório para o App:

  • mkdir src/components/App

Mova todos os arquivos App para esse diretório. Use o curinga * para selecionar quaisquer arquivos que comecem com App., independentemente da extensão. Depois, utilize o comando mv para colocá-los no novo diretório.

  • mv src/App.* src/components/App

Em seguida, atualize o caminho de importação relativo em index.js, que é o componente raiz que inicializa todo o processo.

  • nano src/index.js

A instrução de importação precisa apontar para o arquivo Apps.js no diretório App. Portanto, faça a seguinte alteração destacada:

wrapper-tutorial/src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './components/App/App';
import * as serviceWorker from './serviceWorker';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

Salve e saia do arquivo.

Agora que o projeto está configurado, crie o primeiro componente.

Passo 2 — Coletando props não utilizadas com ...props

Neste passo, você criará um componente para exibir um conjunto de dados sobre um grupo de animais. Seu componente conterá um segundo componente aninhado para exibir algumas informações visualmente. Para conectar o componente pai e o componente aninhado, você vai utilizar os operadores rest e spread para repassar props não utilizadas do pai para o filho, sem que o pai precise estar ciente dos nomes ou tipos das props.

Ao final deste passo, você terá um componente pai que pode fornecer props aos componentes aninhados sem ter que saber quais são as props. Isso manterá o componente pai flexível, permitindo que você atualize o componente filho sem ter que alterar o pai.

Criando um componente AnimalCard

Para começar, crie um conjunto de dados para seus animais. Primeiro, abra um arquivo contendo o conjunto de dados no diretório components/App.

  • nano src/components/App/data.js

Adicione os seguintes dados:

src/components/App/data.js

export default [
  {
    name: 'Lion',
    scientificName: 'Panthero leo',
    size: 140,
    diet: ['meat']
  },
  {
    name: 'Gorilla',
    scientificName: 'Gorilla beringei',
    size: 205,
    diet: ['plants', 'insects']
  },
  {
    name: 'Zebra',
    scientificName: 'Equus quagga',
    size: 322,
    diet: ['plants'],
  }
]

Esta lista de animais é uma matriz de objetos que inclui o nome do animal, nome científico, peso e dieta.

Salve e feche o arquivo.

A seguir, crie um diretório para o componente AnimalCard:

  • mkdir src/components/AnimalCard

Abra um novo arquivo no diretório:

  • nano src/components/AnimalCard/AnimalCard.js

Agora, adicione um componente que irá usar name, diet e size como uma prop e exiba-o:

wrapper-tutorial/src/components/AnimalCard/AnimalCard.js
import React from 'react';
import PropTypes from 'prop-types';

export default function AnimalCard({ diet, name, size }) {
  return(
    <div>
      <h3>{name}</h3>
      <div>{size}kg</div>
      <div>{diet.join(', ')}.</div>
    </div>
  )
}

AnimalCard.propTypes = {
  diet: PropTypes.arrayOf(PropTypes.string).isRequired,
  name: PropTypes.string.isRequired,
  size: PropTypes.number.isRequired,
}

Aqui, você está desestruturando as props na lista de parâmetros para a função AnimalCard, depois mostrando os dados em uma div. Os dados de diet são listados como uma única string usando o método join(). Cada parte de dados inclui uma PropType correspondente para garantir que o tipo de dados esteja correto.

Salve e feche o arquivo.

Agora que você tem seu componente e seus dados, você precisa combiná-los. Para fazer isso, importe o componente e os dados no componente raiz do seu projeto: App.js.

Primeiro, abra o componente:

  • nano src/components/App/App.js

A partir daí, você pode fazer um loop sobre os dados e retornar um novo AnimalCard com as props relevantes. Adicione as linhas destacadas ao App.js:

wrapper-tutorial/src/components/App/App.js
import React from 'react';
import './App.css';

import animals from './data';
import AnimalCard from '../AnimalCard/AnimalCard';

function App() {
  return (
    <div className="wrapper">
      {animals.map(animal =>
        <AnimalCard
          diet={animal.diet}
          key={animal.name}
          name={animal.name}
          size={animal.size}
        />
      )}
    </div>
  );
}

export default App;

Salve e feche o arquivo.

À medida que você trabalha em projetos mais complexos, seus dados virão de lugares mais variados, como APIs, localStorage, ou arquivos estáticos. Mas o processo para usar cada um deles será semelhante: atribuir os dados a uma variável e fazer um loop sobre os dados. Neste caso, o dado é de um arquivo estático, então você está importando diretamente para uma variável.

Neste código, você utiliza o método .map() para iterar sobre animals e exibir as props. Observe que você não precisa usar cada parte de dados. Você não está passando explicitamente a propriedade scientificName, por exemplo. Você também está adicionando uma prop.key separada que o React usará para acompanhar os dados mapeados. Finalmente, você está encapsulando o código com uma div com um className wrapper que você usará para adicionar algum estilo.

Para adicionar este estilo, abra o App.css:

  • nano src/components/App/App.css

Remova o estilo padrão e adicione propriedades flex a uma classe chamada wrapper:

prop-tutorial/src/components/App/App.js
.wrapper {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    padding: 20px;
}

Isso usará o layout flexbox para organizar os dados para que eles se alinhem. padding dá algum espaço na janela do navegador, e justify-content espalha o espaço extra entre os elementos.

Salve e saia do arquivo. Quando fizer isso, o navegador será recarregado e você verá alguns dados espaçados.

Browser with data spaced out

Criando um componente de detalhes

Você tem agora um componente simples que mostra os dados. Mas digamos que você queira dar um pouco de charme aos dados de diet convertendo o texto em um emoji. Você pode fazer isso convertendo os dados em seu componente.

O React foi projetado para ser flexível, então, quando você estiver pensando em como converter dados, terá algumas opções diferentes:

  • Você pode criar uma função dentro do componente que converta o texto para um emoji.
  • Você pode criar uma função e armazená-la em um arquivo fora do componente para que você possa reutilizar a lógica em diferentes componentes.
  • Você pode criar um componente separado que converta o texto em um emoji.

Cada abordagem é boa quando aplicada ao caso de uso correto, e você se encontrará alternando entre elas enquanto você constrói uma aplicação. Para evitar a abstração prematura e complexidade, você deve usar a primeira opção para começar. Se você desejar reutilizar a lógica, poderá extrair a função separadamente do componente. A terceira opção é melhor se você quiser ter uma peça reutilizável que inclua a lógica e o markup, ou se você quiser isolar para usar em toda a aplicação.

Neste caso, vamos criar um novo componente, uma vez que vamos querer adicionar mais dados posteriormente e estamos combinando o markup com a lógica de conversão.

O novo componente será chamado AnimalDetails. Para fazer isso, crie um novo diretório:

  • mkdir src/components/AnimalDetails

Em seguida, abra AnimalDetails.js em seu editor de texto:

  • nano src/components/AnimalDetails/AnimalDetails.js

Dentro do arquivo, faça um pequeno componente que exibe diet como um emoji:

wrapper-tutorial/src/components/AnimalDetails/AnimalDetails.js
import React from 'react';
import PropTypes from 'prop-types';
import './AnimalDetails.css';

function convertFood(food) {
  switch(food) {
    case 'insects':
      return '🐜';
    case 'meat':
      return '🍖';
    case 'plants':
    default:
      return '🌱';
  }
}

export default function AnimalDetails({ diet }) {
  return(
    <div className="details">
      <h4>Details:</h4>
      <div>
        Diet: {diet.map(food => convertFood(food)).join(' ')}
      </div>
    </div>
  )
}

AnimalDetails.propTypes = {
  diet: PropTypes.arrayOf(PropTypes.string).isRequired,
}

O objeto AnimalDetails.propTypes define a função para fazer uma prop de diet que é uma matriz de strings. Em seguida, dentro do componente, o código faz um loop sobre diet e converte a string em um emoji usando a instrução switch.

Salve e feche o arquivo.

Você também está importando o CSS. Desse modo, vamos adicionar isso agora.

Abra AnimalDetails.css:

  • nano src/components/AnimalDetails/AnimalDetails.css

Adicione algum CSS para dar ao elemento uma borda e uma margem para separar os detalhes do restante do componente:

wrapper-tutorial/src/components/AnimalDetails/AnimalDetails.css
.details {
    border-top: gray solid 1px;
    margin: 20px 0;
}

Usamos .details para fazer a correspondência entre a regra e os elementos com uma className details.

Salve e feche o arquivo.

Agora que você tem um novo componente personalizado, você pode adicioná-lo ao seu componente AnimalCard. Abra AnimalCard.js:

  • nano src/components/AnimalCard/AnimalCard.js

Substitua a instrução diet.join pelo novo componente AnimalDetails e passe diet como uma prop adicionando as linhas destacadas:

wrapper-tutorial/src/components/AnimalCard/AnimalCard.js
import React from 'react';
import PropTypes from 'prop-types';
import AnimalDetails from '../AnimalDetails/AnimalDetails';

export default function AnimalCard({ diet, name, size }) {
  return(
    <div>
      <h3>{name}</h3>
      <div>{size}kg</div>
      <AnimalDetails
        diet={diet}
      />
    </div>
  )
}

AnimalCard.propTypes = {
  diet: PropTypes.arrayOf(PropTypes.string).isRequired,
  name: PropTypes.string.isRequired,
  size: PropTypes.number.isRequired,
}

Salve o arquivo e você verá os novos detalhes no navegador.

Browser with details

Passando detalhes através de um componente com ...props

Os componentes estão funcionando bem juntos, mas há uma ligeira ineficiência em AnimalCard. Você está explicitamente extraindo diet do argumento props, mas você não está usando os dados. Em vez disso, você está passando-os através do componente. Não há nada inerentemente errado nisso — de fato, geralmente é melhor errar ao lado de muita comunicação. Mas ao fazer isso, você torna seu código mais difícil de manter. Sempre que você quiser passar novos dados para AnimalDetails, você precisa atualizar três lugares: App, onde você passa as props, AnimalDetails, que consome a prop, e AnimalCard, que é o intermediário.

Uma maneira melhor é reunir quaisquer objetos não utilizados dentro de AnimalCard e, em seguida, passá-los diretamente para AnimalDetails. Isso dá a você a chance de fazer alterações em AnimalDetails sem alterar AnimalCard. De fato, AnimalCard não precisa saber nada sobre as props ou sobre PropTypes que estão entrando em AnimalDetails.

Para fazer isso, você usará o operador rest do objeto. Este operador coleta quaisquer itens que não sejam extraídos durante a desestruturação e os salva em um novo objeto.

Aqui está um exemplo simples:

const dog = {
    name: 'dog',
    diet: ['meat']
}

const { name, ...props  } = dog;

Neste caso, a variável name será 'dog' e as props da variável serão { diet: ['meat']}.

Até agora, você passou todas as props como se elas fossem atributos HTML, mas você também pode usar objetos para enviar props. Para usar um objeto como uma prop, você precisa usar o operador de espalhamento (spread) —...props— entre chaves. Isso mudará cada par de chave-valor em uma prop.

Abra AnimalCard.js:

  • nano src/components/AnimalCard/AnimalCard.js

Lá dentro, remova diet do objeto desestruturado e, em vez disso, colete o resto das props em uma variável chamada props. Em seguida, passe esses props diretamente para AnimalDetails:

wrapper-tutorial/src/components/AnimalCard/AnimalCard.js
import React from 'react';
import PropTypes from 'prop-types';
import AnimalDetails from '../AnimalDetails/AnimalDetails';

export default function AnimalCard({ name, size, ...props }) {
  return(
    <div>
      <h3>{name}</h3>
      <div>{size}kg</div>
      <AnimalDetails
        {...props}
      />
    </div>
  )
}

AnimalCard.propTypes = {
  name: PropTypes.string.isRequired,
  size: PropTypes.number.isRequired,
}

Observe que você pode remover o PropType diet, uma vez que você não está usando a prop neste componente.

Neste caso, você está apenas passando uma prop para AnimalDetails. Em casos onde você tem várias props, a ordem fará diferença. Uma prop posterior substituirá as props anteriores; portanto, se você tiver uma prop que queira ter prioridade, verifique se ela é a última. Isso pode causar alguma confusão se seu objeto props tiver uma propriedade que também seja um valor nomeado.

Salve e feche o arquivo. O navegador irá atualizar e tudo ficará igual:

Browser with details

Para ver como o objeto ...props adiciona flexibilidade, vamos passar scientificName para AnimalDetails através do componente AnimalCard.

Primeiro, abra o App.js:

  • nano src/components/App/App.js

Em seguida, passe scientificName como uma prop:

wrapper-tutorial/src/components/App/App.js
import React from 'react';
import './App.css';

import animals from './data';
import AnimalCard from '../AnimalCard/AnimalCard';

function App() {
  return (
    <div className="wrapper">
      {animals.map(animal =>
        <AnimalCard
          diet={animal.diet}
          key={animal.name}
          name={animal.name}
          size={animal.size}
          scientificName={animal.scientificName}
        />
      )}
    </div>
  );
}

export default App;

Salve e feche o arquivo.

Pule AnimalCard; você não precisará fazer nenhuma alteração lá. Em seguida, abra AnimalDetails para que você possa consumir a nova prop:

  • nano src/components/AnimalDetails/AnimalDetails.js

A nova prop será uma string, que você adicionará à lista details junto com uma linha declarando o PropType:

wrapper-tutorial/src/components/AnimalDetails/AnimalDetails.js
import React from 'react';
...
export default function AnimalDetails({ diet, scientificName }) {
  return(
    <div className="details">
      <h4>Details:</h4>
      <div>
        Scientific Name: {scientificName}.
      </div>
      <div>
        Diet: {diet.map(food => convertFood(food)).join(' ')}
      </div>
    </div>
  )
}

AnimalDetails.propTypes = {
  diet: PropTypes.arrayOf(PropTypes.string).isRequired,
  scientificName: PropTypes.string.isRequired,
}

Salve e feche o arquivo. Quando fizer isso, o navegador irá atualizar e você verá os novos detalhes sem quaisquer alterações no componente AnimalCard:

Browser with scientific name

Neste passo, você aprendeu como criar props pais flexíveis que podem pegar props desconhecidas e passá-las em componentes aninhados com o operador de espalhamento. Este é um padrão comum que lhe dará a flexibilidade que você precisa para criar componentes com responsabilidades focadas. No próximo passo, você criará componentes que podem pegar componentes desconhecidos como uma prop usando a prop embutida children.

Passo 3 — Criando componentes de encapsulamento com children

Neste passo, você criará um componente de encapsulamento que pode pegar um grupo desconhecido de componentes como uma prop. Isso lhe dará a capacidade de aninhar componentes como HTML padrão, e isto lhe dará um padrão para criar encapsulamentos reutilizáveis, permitindo que você crie uma variedade de componentes que precisam de um projeto comum, porém com um interior flexível.

O React lhe fornece uma prop embutida chamado children que coleta quaisquer componentes children. Usar isso torna a criação de componentes de encapsulamento intuitiva e legível.

Para começar, crie um novo componente chamado Card. Este será um componente de encapsulamento para criar um estilo padrão para qualquer novo componente card.

Crie um novo diretório:

  • mkdir src/components/Card

Em seguida, abra o componente Card em seu editor de texto:

  • nano src/components/Card/Card.js

Crie um componente que pegue children e title como props e os encapsule em uma div adicionando o seguinte código:

wrapper-tutorial/src/components/Card/Card.js
import React from 'react';
import PropTypes from 'prop-types';
import './Card.css';

export default function Card({ children, title }) {
  return(
    <div className="card">
      <div className="card-details">
        <h2>{title}</h2>
      </div>
      {children}
    </div>
  )
}

Card.propTypes = {
  children: PropTypes.oneOfType([
    PropTypes.arrayOf(PropTypes.element),
    PropTypes.element.isRequired
  ]),
  title: PropTypes.string.isRequired,
}

Os PropTypes para children são novos. A prop children pode ser tanto um elemento JSX como uma matriz de elementos JSX. title é uma string.

Salve e feche o arquivo.

Em seguida, adicione algum estilo. Abra o Card.css:

  • nano src/components/Card/Card.css

Seu cartão terá uma borda e uma linha embaixo dos detalhes.

wrapper-tutorial/src/components/Card/Card.css
.card {
    border: black solid 1px;
    margin: 10px;
    padding: 10px;
    width: 200px;
}

.card-details {
    border-bottom: gray solid 1px;
    margin-bottom: 20px;
}

Salve e feche o arquivo. Agora que você tem seu componente você precisa usá-lo. Você pode encapsular cada AnimalCard com o componente Card no App.js, mas como o nome AnimalCard implica que ele já seja um Card, seria melhor usar o componente Card dentro de AnimalCard.

Abra AnimalCard:

  • nano src/components/AnimalCard/AnimalCard.js

Diferentemente de outras props, você não passa children explicitamente. Em vez disso, você inclui o JSX como se eles fossem elementos HTML filhos. Em outras palavras, você apenas os aninha dentro do elemento, como a seguir:

wrapper-tutorial/src/components/AnimalCard/AnimalCard.js
import React from 'react';
import PropTypes from 'prop-types';
import Card from '../Card/Card';
import AnimalDetails from '../AnimalDetails/AnimalDetails';

export default function AnimalCard({ name, size, ...props }) {
  return(
    <Card title="Animal">
      <h3>{name}</h3>
      <div>{size}kg</div>
      <AnimalDetails
        {...props}
      />
    </Card>
  )
}

AnimalCard.propTypes = {
  name: PropTypes.string.isRequired,
  size: PropTypes.number.isRequired,
}

Diferentemente de um componente React, você não precisa ter um único elemento raiz como um filho. É por isso que o PropType para Card especificou que ele poderia ser uma matriz de elementos ou um único elemento. Além de passar children como componentes aninhados, você está dando ao cartão um título Animal.

Salve e feche o arquivo. Quando você fizer isso, o navegador irá atualizar e você verá o componente do cartão atualizado.

Browser with cards

Agora, você tem um componente Card reutilizável que pode pegar qualquer número de children aninhados. A principal vantagem disso é que você pode reutilizar o Card com qualquer componente arbitrário. Se você quisesse criar um cartão Plant, você poderia fazer isso encapsulando as informações de plant com o componente Card. Ele nem mesmo precisa se relacionar: se você quisesse reutilizar o componente Card em uma aplicação completamente diferente que enumera coisas como música ou dados de conta, você também poderia fazer isso. O componente Card não liga para o que os children são; você está apenas reutilizando o elemento de encapsulamento, que neste caso é a borda estilizada e o título.

A desvantagem de usar children é que você pode ter apenas uma instância da prop child. Ocasionalmente, você desejará que um componente tenha JSX personalizado em vários locais Felizmente, você pode fazer isso passando componentes JSX e React como props, que abordaremos no próximo passo.

Passo 4 — Passando componentes como props

Neste passo, você modificará seu componente Card para pegar outros componentes como props. Isso dará ao seu componente flexibilidade máxima para exibir componentes desconhecidos ou JSX em vários locais da página. Diferentemente de children, que você só pode usar uma única vez, você pode ter tantos componentes quanto props, dando ao seu componente de encapsulamento a capacidade de se adaptar a uma variedade de necessidades, mantendo uma aparência e uma estrutura padrão.

Ao final deste passo, você terá um componente que pode encapsular componentes children e exibir outros componentes no cartão. Este padrão lhe dará flexibilidade quando você precisar criar componentes que precisam de informações mais complexas do que strings simples e números inteiros.

Vamos modificar o componente Card para pegar um elemento React arbitrário chamado details.

Primeiro, abra o componente Card :

  • nano src/components/Card/Card.js

Em seguida, adicione uma nova prop chamada details e a coloque abaixo do elemento <h2>:

wrapper-tutorial/src/components/Card/Card.js
import React from 'react';
import PropTypes from 'prop-types';
import './Card.css';

export default function Card({ children, details, title }) {
  return(
    <div className="card">
      <div className="card-details">
        <h2>{title}</h2>
        {details}
      </div>
      {children}
    </div>
  )
}

Card.propTypes = {
  children: PropTypes.oneOfType([
    PropTypes.arrayOf(PropTypes.element),
    PropTypes.element.isRequired
  ]),
  details: PropTypes.element,
  title: PropTypes.string.isRequired,
}

Card.defaultProps = {
  details: null,
}

Esta prop terá o mesmo tipo de children, mas ela deve ser opcional. Para torná-la opcional, você adiciona um valor padrão de null. Neste caso, se um usuário não passar detalhes, o componente ainda será válido e não exibirá nada a mais.

Salve e feche o arquivo. A página irá atualizar e você verá a mesma imagem como antes:

Browser with cards

Agora, adicione alguns detalhes ao AnimalCard. Primeiro, abra AnimalCard.

  • nano src/components/AnimalCard/AnimalCard.js

Como o componente Card já está usando children, você precisará passar o novo componente JSX como uma prop. Como esses são todos mamíferos, adicione isso ao cartão, mas encapsule-o em tags <em> para torná-lo itálico.

wrapper-tutorial/src/components/AnimalCard/AnimalCard.js
import React from 'react';
...

export default function AnimalCard({ name, size, ...props }) {
  return(
    <Card title="Animal" details={<em>Mammal</em>}>
      <h3>{name}</h3>
      <div>{size}kg</div>
      <AnimalDetails
        {...props}
      />
    </Card>
  )
}
...

Salve o arquivo. Quando você fizer isso, o navegador irá atualizar e você verá a atualização, incluindo a palavra Mammal.

Browser with card and details

Esta prop já é poderosa, pois ela pode pegar JSX de qualquer tamanho. Neste exemplo, você adicionou apenas um único elemento, mas você poderia passar o tanto de JSX quanto você queira. E também não precisa ser JSX. Se você tivesse um markup complicado por exemplo, você não iria querer passá-lo diretamente na prop; isso seria difícil de ler. Em vez disso, você poderia criar um componente separado e, em seguida, passar o componente como uma prop.

Para ver isto funcionando, passe AnimalDetails para a prop details:

wrapper-tutorial/src/components/AnimalCard/AnimalCard.js
import React from 'react';
...

export default function AnimalCard({ name, size, ...props }) {
  return(
    <Card
      title="Animal"
      details={
        <AnimalDetails
          {...props}
        />
      }
    >
      <h3>{name}</h3>
      <div>{size}kg</div>
    </Card>
  )
}
...

AnimalDetails é mais complicado e tem uma série de linhas de markup. Se você fosse adicioná-lo diretamente a details, ele aumentaria a prop substancialmente e o tornaria difícil de ler.

Salve e feche o arquivo. Quando você fizer isso, o navegador irá atualizar e os detalhes irão aparecer no topo do cartão.

Card with details at the top

Agora, você tem um componente Card que pode pegar JSX personalizado e colocá-lo em vários pontos. Você não está restrito a uma única prop; você pode passar elementos para quantas props desejar. Isso lhe dá a capacidade de criar componentes de encapsulamento flexíveis que podem dar a outros desenvolvedores a oportunidade de personalizar um componente, mantendo seu estilo e funcionalidade gerais.

Passar um componente como uma prop não é perfeito. É um pouco mais difícil de ler e não é tão claro quanto passar children, mas eles são tão flexíveis e você pode usar quantos deles desejar em um componente. Você deve usar children primeiro, mas não hesite em voltar para props, caso isso não seja o suficiente.

Neste passo, você aprendeu como passar componentes JSX e React como props para outro componente. Isso dará ao seu componente a flexibilidade para lidar com muitas situações onde um componente de encapsulamento pode precisar de várias props para lidar com JSX ou com componentes.

Conclusão

Você criou uma variedade de componentes de encapsulamento que podem exibir dados de maneira flexível, mantendo uma aparência e uma estrutura previsíveis. Você criou componentes que podem coletar e passar props desconhecidas para componentes aninhados. Você também usou a prop embutido children para criar componentes de encapsulamento que podem lidar com um número arbitrário de elementos aninhados. Por fim, você criou um componente que pode pegar componentes JSX ou React como uma prop para que seu componente de encapsulamento possa lidar com várias instâncias diferentes de personalizações.

Componentes de encapsulamento lhe fornecem a capacidade de se adaptar a circunstâncias desconhecidas, ao mesmo tempo em que maximiza a reutilização de código e a consistência. Este padrão é útil para criar elementos de UI básicos que você reutilizará ao longo de uma aplicação, incluindo: botões, alertas, modais, slide shows, e muito mais. Você se verá voltando a ele muitas vezes.

Se você quiser analisar mais tutoriais de React, dê uma olhada em nossa página sobre o React ou retorne à página da série Como Programar em React.js.

0 Comments

Creative Commons License