Componentes compostos em React
Publicados: 2022-03-10Os componentes compostos ajudam os desenvolvedores a criar APIs mais expressivas e flexíveis para compartilhar estado e lógica nos componentes. Este tutorial explica como isso pode ser feito com a ajuda do Context API e React para construir componentes usando este padrão avançado.
Observação : para poder acompanhar, você precisará de um conhecimento básico do React e de como a Context API funciona.
O que é um componente composto?
Componentes compostos podem ser considerados um padrão que encerra o estado e o comportamento de um grupo de componentes, mas ainda dá o controle de renderização de suas partes variáveis de volta ao usuário externo.
A partir da definição acima, tomando nota das palavras-chave: estado e comportamento . Isso nos ajuda a entender que o componente composto lida com o estado (ou seja, como o estado se comporta em um componente que é delimitado por um usuário externo sendo o pai do componente).
O objetivo dos componentes compostos é fornecer uma API mais expressiva e flexível para comunicação entre os componentes pai e filho.
Pense nisso como as tags <select>
e <option>
em HTML:
<select> <option value="volvo">Volvo</option> <option value="mercedes">Mercedes</option> <option value="audi">Audi</option> </select>
A tag select
funciona em conjunto com a tag option
que é usada para um menu suspenso para selecionar itens em HTML. Aqui o <select>
gerencia o estado da UI, então os elementos <option>
são configurados sobre como o <select>
deve funcionar. Componentes compostos no React são usados para construir um componente de UI declarativo que ajuda a evitar o drill de prop.
A perfuração de props está passando adereços para vários componentes filhos. Isso também é o que eles chamam de “cheiro de código”. A pior parte da perfuração de props é que quando o componente pai é renderizado novamente, os componentes filhos também serão renderizados novamente e causarão um efeito dominó no componente. Uma boa solução seria usar a API React Context, que também veremos mais tarde.
Aplicando componentes compostos em React
Esta seção explica os pacotes que podemos usar em nossa aplicação que adotam o padrão de componentes compostos para construir componentes em React. Este exemplo é um componente Menu
do pacote @reach
UI.
import { Menu, MenuList, MenuButton, MenuItem, MenuItems, MenuPopover, MenuLink, } from "@reach/menu-button"; import "@reach/menu-button/styles.css";
Aqui está uma maneira de usar o componente Menu
:
function Example() { return ( <Menu> <MenuButton>Actions</MenuButton> <MenuList> <MenuItem>Download</MenuItem> <MenuLink to="view">View</MenuLink> </MenuList> </Menu> ); }
O código de exemplo acima é uma das implementações de componentes compostos em que você pode ver que Menu
, MenuButton
, MenuList
, MenuItem
e MenuLink
foram todos importados de @reach/menu-button
. Ao contrário de exportar um único componente, ReachUI exporta um componente pai que é Menu
que acompanha seus componentes filhos que são MenuButton
, MenuList
, MenuItem
e MenuLink
.
Quando você deve fazer uso de componentes compostos?
Como desenvolvedor React, você deve usar componentes compostos quando quiser:
- Resolver problemas relacionados à construção de componentes reutilizáveis;
- Desenvolvimento de componentes altamente coesos com acoplamento mínimo;
- Melhores maneiras de compartilhar lógica entre componentes.
Prós e contras de componentes compostos
Um componente composto é um padrão incrível do React para adicionar ao seu kit de ferramentas do desenvolvedor React. Nesta seção, apresentarei os prós e os contras de usar componentes compostos e o que aprendi ao construir componentes usando esse padrão de desenvolvimento.
Prós
Separação de preocupações
Ter toda a lógica de estado da interface do usuário no componente pai e comunicar isso internamente a todos os componentes filho cria uma divisão clara de responsabilidade.Complexidade Reduzida
Ao contrário da perfuração de prop para passar propriedades para seus componentes específicos, props filho vão para seus respectivos componentes filho usando o padrão de componente composto.
Contras
Um dos maiores contras de construir componentes no React com o padrão de componente composto é que apenas direct children
do componente pai terão acesso aos adereços, o que significa que não podemos envolver nenhum desses componentes em outro componente.
export default function FlyoutMenu() { return ( <FlyOut> {/* This breaks */} <div> <FlyOut.Toggle /> <FlyOut.List> <FlyOut.Item>Edit</FlyOut.Item> <FlyOut.Item>Delete</FlyOut.Item> </FlyOut.List> </div> </FlyOut> ); }
Uma solução para esse problema seria usar o padrão de componente composto flexível para compartilhar implicitamente o estado usando a API React.createContext
.
A API de contexto torna possível passar o estado do React por meio de componentes aninhados ao construir usando o padrão de componente composto de componentes de construção no React. Isso é possível porque o context
fornece uma maneira de passar dados pela árvore de componentes sem ter que passar adereços manualmente em todos os níveis. O uso da Context API oferece muita flexibilidade ao usuário final.
Mantendo componentes compostos em React
Os componentes compostos fornecem uma maneira mais flexível de compartilhar o estado em aplicativos React, portanto, usar componentes compostos em seus aplicativos React facilita a manutenção e a depuração de seus aplicativos.
Construindo uma demonstração
Neste artigo, vamos construir um componente de acordeão em React usando o padrão de componentes compostos. O componente que vamos construir neste tutorial seria um componente de acordeão personalizado que é flexível e compartilha o estado dentro do componente usando a API de contexto.
Vamos lá!
Primeiro de tudo, vamos criar um aplicativo React usando o seguinte:
npx create-react-app accordionComponent cd accordionComponent npm start
ou
yarn create react-app accordionComponent cd accordionComponent yarn start
Os comandos acima criam um aplicativo React, alteram o diretório para o projeto React e iniciam o servidor de desenvolvimento.
Nota : Neste tutorial, usaremos styled-components
para ajudar a estilizar nossos componentes.
Use o comando abaixo para instalar styled-components
:
yarn add styled-components
ou
npm install --save styled-components
Na pasta src , crie uma nova pasta chamada components . É aqui que todos os nossos componentes viveriam. Dentro da pasta de componentes , crie dois novos arquivos: accordion.js
e accordion.styles.js
.
O arquivo accordion.styles.js
contém nosso estilo para o componente Accordion
(nosso estilo foi feito usando styled-components
).
import styled from "styled-components"; export const Container = styled.div` display: flex; border-bottom: 8px solid #222; `;
Acima está um exemplo de componentes de estilo usando a biblioteca css-in-js
chamada styled-components
.
No arquivo accordion.styles.js
, adicione os estilos restantes:
export const Frame = styled.div` margin-bottom: 40px; `; export const Inner = styled.div` display: flex; padding: 70px 45px; flex-direction: column; max-width: 815px; margin: auto; `; export const Title = styled.h1` font-size: 40px; line-height: 1.1; margin-top: 0; margin-bottom: 8px; color: black; text-align: center; `; export const Item = styled.div` color: white; margin: auto; margin-bottom: 10px; max-width: 728px; width: 100%; &:first-of-type { margin-top: 3em; } &:last-of-type { margin-bottom: 0; } `; export const Header = styled.div` display: flex; flex-direction: space-between; cursor: pointer; margin-bottom: 1px; font-size: 26px; font-weight: normal; background: #303030; padding: 0.8em 1.2em 0.8em 1.2em; user-select: none; align-items: center; img { filter: brightness(0) invert(1); width: 24px; user-select: none; @media (max-width: 600px) { width: 16px; } } `; export const Body = styled.div` font-size: 26px; font-weight: normal; line-height: normal; background: #303030; white-space: pre-wrap; user-select: none; overflow: hidden; &.closed { max-height: 0; overflow: hidden; transition: max-height 0.25ms cubic-bezier(0.5, 0, 0.1, 1); } &.open { max-height: 0px; transition: max-height 0.25ms cubic-bezier(0.5, 0, 0.1, 1); } span { display: block; padding: 0.8em 2.2em 0.8em 1.2em; } `;
Vamos começar a construir nosso componente acordeão. No arquivo accordion.js
, vamos adicionar o seguinte código:
import React, { useState, useContext, createContext } from "react"; import { Container, Inner, Item, Body, Frame, Title, Header } from "./accordion.styles";
Acima, estamos importando os useState
, useContext
e createContext
que nos ajudarão a construir nosso componente de acordeão usando componentes compostos.
A documentação do React explica que o context
ajuda a fornecer uma maneira de passar dados pela árvore de componentes sem ter que passar props manualmente em todos os níveis.
Observando o que importamos anteriormente em nosso arquivo accordion.js
, você notará que também importamos nossos estilos como componentes que nos ajudarão a construir nossos componentes mais rapidamente.
Iremos em frente e criaremos nosso contexto para o componente que compartilhará dados com os componentes que precisam deles:
const ToggleContext = createContext(); export default function Accordion({ children, ...restProps }) { return ( <Container {...restProps}> <Inner>{children}</Inner> </Container> ); }
Os componentes Container
e Inner
do trecho de código acima são do nosso arquivo ./accordion.styles.js
no qual criamos estilos para nossos componentes usando os styled-components
(da biblioteca css-in-js
). O componente Container
abriga todo o Accordion
que estamos construindo usando componentes compostos.
Aqui estamos criando um objeto de contexto usando o createContext()
, então quando o React renderizar um componente que se inscreve neste objeto Context, ele lerá o valor do contexto atual do Provedor correspondente mais próximo acima dele na árvore.
Então também estamos criando nosso componente base que é o Acordeon; leva os children
e qualquer restProps
. Este é o nosso componente pai que abriga os componentes filhos do acordeão.
Vamos criar outros componentes filhos dentro do arquivo accordion.js
:
Accordion.Title = function AccordionTitle({ children, ...restProps }) { return <Title {...restProps}>{children}</Title>; }; Accordion.Frame = function AccordionFrame({ children, ...restProps }) { return <Frame {...restProps}>{children}</Frame>; };
Observe o .
após o componente pai Acordeão; isso é usado para conectar o componente filho ao seu componente pai.
Vamos continuar. Agora adicione o seguinte ao arquivo accordion.js
:
Accordion.Item = function AccordionItem({ children, ...restProps }) { const [toggleShow, setToggleShow] = useState(true); return ( <ToggleContext.Provider value={{ toggleShow, setToggleShow }}> <Item {...restProps}>{children}</Item> </ToggleContext.Provider> ); }; Accordion.ItemHeader = function AccordionHeader({ children, ...restProps }) { const { isShown, toggleIsShown } = useContext(ToggleContext); return ( <Header onClick={() => toggleIsShown(!isShown)} {...restProps}> {children} </Header> ); }; Accordion.Body = function AccordionHeader({ children, ...restProps }) { const { isShown } = useContext(ToggleContext); return ( <Body className={isShown ? "open" : "close"}> <span>{children}</span> </Body> ); };
Então aqui estamos criando um componente Body
, Header
e Item
que são todos filhos do componente pai Accordion
. É aqui que pode começar a ficar complicado. Além disso, observe que cada componente filho criado aqui também recebe um prop e restprops
children
.
A partir do componente filho Item
, inicializamos nosso estado usando o gancho useState
e o configuramos como true. Lembre-se também de que criamos um ToggleContext
no nível superior do arquivo accordion.js
que é um Context Object
, e quando o React renderiza um componente que se inscreve nesse objeto Context, ele lerá o valor de contexto atual do provedor correspondente mais próximo acima dele na árvore.
Cada objeto Context vem com um componente Provider
React que permite que os componentes de consumo assinem as alterações de contexto.
O componente provider
aceita um value
prop a ser passado para componentes consumidores que são descendentes deste provedor, e aqui estamos passando o valor do estado atual que é o toggleShow
e o método para definir o valor do estado atual setToggleShow
. Eles são o valor que determina como nosso objeto de contexto compartilhará o estado em torno de nosso componente sem perfuração de prop.
Então, em nosso componente filho de header
do Accordion
, estamos destruindo os valores do objeto de contexto e, em seguida, alterando o estado atual do toggleShow
ao clicar. Então o que estamos tentando fazer é esconder ou mostrar nosso acordeão quando o cabeçalho é clicado.
Em nosso componente Accordion.Body
, também estamos destruindo o toggleShow
que é o estado atual do componente, então dependendo do valor de toggleShow
, podemos ocultar o corpo ou mostrar o conteúdo do componente Accordion.Body
.
Então isso é tudo para o nosso arquivo accordion.js
.
Agora é aqui que podemos ver como tudo o que aprendemos sobre os componentes Context
e Compound components
se juntam. Mas antes disso, vamos criar um novo arquivo chamado data.json
e colar o conteúdo abaixo nele:
[ { "id": 1, "header": "What is Netflix?", "body": "Netflix is a streaming service that offers a wide variety of award-winning TV programs, films, anime, documentaries and more – on thousands of internet-connected devices.\n\nYou can watch as much as you want, whenever you want, without a single advert – all for one low monthly price. There's always something new to discover, and new TV programs and films are added every week!" }, { "id": 2, "header": "How much does Netflix cost?", "body": "Watch Netflix on your smartphone, tablet, smart TV, laptop or streaming device, all for one low fixed monthly fee. Plans start from £5.99 a month. No extra costs or contracts." }, { "id": 3, "header": "Where can I watch?", "body": "Watch anywhere, anytime, on an unlimited number of devices. Sign in with your Netflix account to watch instantly on the web at netflix.com from your personal computer or on any internet-connected device that offers the Netflix app, including smart TVs, smartphones, tablets, streaming media players and game consoles.\n\nYou can also download your favorite programs with the iOS, Android, or Windows 10 app. Use downloads to watch while you're on the go and without an internet connection. Take Netflix with you anywhere." }, { "id": 4, "header": "How do I cancel?", "body": "Netflix is flexible. There are no annoying contracts and no commitments. You can easily cancel your account online with two clicks. There are no cancellation fees – start or stop your account at any time." }, { "id": 5, "header": "What can I watch on Netflix?", "body": "Netflix has an extensive library of feature films, documentaries, TV programs, anime, award-winning Netflix originals, and more. Watch as much as you want, any time you want." } ]
Esses são os dados com os quais trabalharemos para testar nosso componente de acordeão.
Então vamos continuar. Estamos quase terminando e acredito que você aprendeu muito ao seguir este artigo.
Nesta seção, vamos reunir tudo o que temos trabalhado e aprendido sobre componentes compostos para poder usá-lo em nosso arquivo App.js
para usar a função Array.map
para exibir os dados que já temos na web página. Observe também que não houve uso de state dentro do App.js
; tudo o que fizemos foi passar os dados para os componentes específicos e a Context API cuidou de todas as outras coisas.
Agora vamos para a parte final. No seu App.js
, faça o seguinte:
import React from "react"; import Accordion from "./components/Accordion"; import faqData from "./data"; export default function App() { return ( <Accordion> <Accordion.Title>Frequently Asked Questions</Accordion.Title> <Accordion.Frame> {faqData.map((item) => ( <Accordion.Item key={item.id}> <Accordion.Header>{item.header}</Accordion.Header> <Accordion.Body>{item.body}</Accordion.Body> </Accordion.Item> ))} </Accordion.Frame> </Accordion> ); }
Em seu arquivo App.js , importamos nosso acordeão de componente composto do caminho do arquivo, depois também importamos nossos dados fictícios, mapeados através dos dados fictícios para obter os itens individuais em nosso arquivo de dados e exibi-los de acordo com o respectivo componente, você também notaria que tudo o que tínhamos que fazer era passar os filhos para o respectivo componente, a API de Contexto se encarrega de garantir que ele chegue ao componente certo e não houve perfuração de prop.
É assim que nosso produto final deve ficar:
Alternativa aos componentes compostos
Uma alternativa ao uso de componentes compostos seria usar a API Render Props. O termo Render Prop no React se refere a uma técnica para compartilhar código entre componentes do React usando uma prop cujo valor é uma função. Um componente com um prop de renderização pega uma função que retorna um elemento React e o chama em vez de implementar sua própria lógica de renderização.
Passar dados de um componente para um componente filho que precisa dos dados pode resultar na perfuração de suporte quando você tem componentes aninhados entre si. Essa é a vantagem de usar o Contexto para compartilhar dados entre os componentes em vez de usar o método render prop.
Conclusão
Neste artigo, aprendemos sobre um dos padrões avançados do React que é o padrão de componentes compostos. É um método incrível para construir componentes reutilizáveis no React usando o padrão de componente composto para construir seu componente oferece muita flexibilidade em seu componente. Você ainda pode optar por usar o Render Prop se a flexibilidade não for o que seu componente exige no momento.
Os componentes compostos são mais úteis na construção de sistemas de projeto. Também passamos pelo processo de compartilhar o estado dentro dos componentes usando a Context API.
- O código para este tutorial pode ser encontrado no Codesandbox.