Componentes compuestos en reacción

Publicado: 2022-03-10
Resumen rápido ↬ Un componente compuesto es uno de los patrones avanzados de React que utiliza una forma interesante de comunicar la relación entre los componentes de la interfaz de usuario y compartir el estado implícito al aprovechar una relación padre-hijo explícita.

Los componentes compuestos ayudan a los desarrolladores a crear API más expresivas y flexibles para compartir el estado y la lógica dentro de los componentes. Este tutorial explica cómo se puede lograr esto con la ayuda del uso de Context API y React para construir componentes usando este patrón avanzado.

Nota : para poder seguir, necesitará una comprensión básica de React y cómo funciona la API de contexto.

¿Qué es un componente compuesto?

Se puede decir que los componentes compuestos son un patrón que encierra el estado y el comportamiento de un grupo de componentes, pero aún devuelve el control de representación de sus partes variables al usuario externo.

De la definición anterior, tomando nota de las palabras clave: estado y comportamiento . Esto nos ayuda a comprender que el componente compuesto se ocupa del estado (es decir, cómo se comporta el estado en un componente que está encerrado por un usuario externo que es el padre del componente).

El objetivo de los componentes compuestos es proporcionar una API más expresiva y flexible para la comunicación entre los componentes principal y secundario.

Piense en ello como las etiquetas <select> y <option> en HTML:

 <select> <option value="volvo">Volvo</option> <option value="mercedes">Mercedes</option> <option value="audi">Audi</option> </select>

La etiqueta de select funciona junto con la etiqueta de option que se utiliza para un menú desplegable para seleccionar elementos en HTML. Aquí <select> administra el estado de la interfaz de usuario, luego los elementos <option> se configuran sobre cómo debería funcionar <select> . Los componentes compuestos en React se utilizan para crear un componente de interfaz de usuario declarativo que ayuda a evitar la perforación de accesorios.

La perforación de puntales está pasando puntales por varios componentes secundarios. Esto también es lo que llaman un "olor a código". La peor parte de la perforación de prop es que cuando el componente principal se vuelve a renderizar, los componentes secundarios también se volverán a renderizar y provocarán un efecto dominó en el componente. Una buena solución sería usar la API React Context, que también veremos más adelante.

¡Más después del salto! Continúe leyendo a continuación ↓

Aplicar componentes compuestos en React

Esta sección explica los paquetes que podemos utilizar en nuestra aplicación que adoptan el patrón de componente compuesto de componentes de construcción en React. Este ejemplo es un componente de Menu del paquete de interfaz de usuario @reach .

 import { Menu, MenuList, MenuButton, MenuItem, MenuItems, MenuPopover, MenuLink, } from "@reach/menu-button"; import "@reach/menu-button/styles.css";

Aquí hay una forma en que puede usar el componente Menu :

 function Example() { return ( <Menu> <MenuButton>Actions</MenuButton> <MenuList> <MenuItem>Download</MenuItem> <MenuLink to="view">View</MenuLink> </MenuList> </Menu> ); }

El código de ejemplo anterior es una de las implementaciones de componentes compuestos en los que puede ver que Menu , MenuButton , MenuList , MenuItem y MenuLink se importaron todos desde @reach/menu-button . A diferencia de exportar un solo componente, ReachUI exporta un componente principal que es Menu que acompaña a sus componentes secundarios, que son MenuButton , MenuList , MenuItem y MenuLink .

¿Cuándo debería hacer uso de los componentes compuestos?

Como desarrollador de React, debe utilizar componentes compuestos cuando desee:

  • Resolver problemas relacionados con la construcción de componentes reutilizables;
  • Desarrollo de componentes altamente cohesivos con acoplamiento mínimo;
  • Mejores formas de compartir lógica entre componentes.

Pros y contras de los componentes compuestos

Un componente compuesto es un patrón de React increíble para agregar a su kit de herramientas de desarrollador de React. En esta sección, explicaré los pros y los contras de usar componentes compuestos y lo que he aprendido al construir componentes usando este patrón de desarrollo.

ventajas

  • Separación de preocupaciones
    Tener toda la lógica de estado de la interfaz de usuario en el componente principal y comunicarla internamente a todos los componentes secundarios permite una clara división de responsabilidades.

  • Complejidad reducida
    A diferencia de la exploración de accesorios para pasar propiedades a sus componentes específicos, los accesorios secundarios van a sus respectivos componentes secundarios utilizando el patrón de componente compuesto.

Contras

Una de las principales desventajas de crear componentes en React con el patrón de componente compuesto es que solo los elementos secundarios direct children del componente principal tendrán acceso a los accesorios, lo que significa que no podemos envolver ninguno de estos componentes en otro 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> ); }

Una solución a este problema sería usar el patrón de componente compuesto flexible para compartir implícitamente el estado usando la API React.createContext .

La API de contexto hace posible pasar el estado de React a través de componentes anidados cuando se construye utilizando el patrón de componente compuesto de los componentes de construcción en React. Esto es posible porque el context proporciona una forma de pasar datos al árbol de componentes sin tener que pasar los accesorios manualmente en cada nivel. Hacer uso de la API de contexto proporciona mucha flexibilidad al usuario final.

Mantenimiento de componentes compuestos en React

Los componentes compuestos brindan una forma más flexible de compartir el estado dentro de las aplicaciones React, por lo que el uso de componentes compuestos en sus aplicaciones React facilita el mantenimiento y la depuración de sus aplicaciones.

Creación de una demostración

En este artículo, vamos a construir un componente de acordeón en React usando el patrón de componentes compuestos. El componente que vamos a crear en este tutorial sería un componente de acordeón personalizado que es flexible y comparte el estado dentro del componente mediante la API de contexto.

¡Vamos!

En primer lugar, creemos una aplicación React usando lo siguiente:

 npx create-react-app accordionComponent cd accordionComponent npm start

o

 yarn create react-app accordionComponent cd accordionComponent yarn start

Los comandos anteriores crean una aplicación React, cambian el directorio al proyecto React e inician el servidor de desarrollo.

Nota : En este tutorial, utilizaremos styled-components con estilo para ayudar a diseñar nuestros componentes.

Use el siguiente comando para instalar styled-components :

 yarn add styled-components

o

 npm install --save styled-components

En la carpeta src , cree una nueva carpeta llamada components . Aquí es donde vivirían todos nuestros componentes. Dentro de la carpeta de componentes , cree dos nuevos archivos: accordion.js y accordion.styles.js .

El archivo accordion.styles.js contiene nuestro estilo para el componente Accordion (nuestro estilo se realizó usando styled-components ).

 import styled from "styled-components"; export const Container = styled.div` display: flex; border-bottom: 8px solid #222; `;

Arriba hay un ejemplo de componentes de diseño usando la biblioteca css-in-js llamada styled-components .

Dentro del archivo accordion.styles.js , agregue los 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; } `;

Comencemos a construir nuestro componente de acordeón. En el archivo accordion.js , agreguemos el siguiente código:

 import React, { useState, useContext, createContext } from "react"; import { Container, Inner, Item, Body, Frame, Title, Header } from "./accordion.styles";

Arriba, estamos importando los useState , useContext y createContext que nos ayudarán a construir nuestro componente de acordeón usando componentes compuestos.

La documentación de React explica que el context ayuda a proporcionar una forma de pasar datos a través del árbol de componentes sin tener que pasar los accesorios manualmente en cada nivel.

Si observa lo que hemos importado anteriormente en nuestro archivo accordion.js , notará que también importamos nuestros estilos como componentes, lo que nos ayudará a construir nuestros componentes más rápido.

Continuaremos y crearemos nuestro contexto para el componente que compartirá datos con los componentes que los necesitan:

 const ToggleContext = createContext(); export default function Accordion({ children, ...restProps }) { return ( <Container {...restProps}> <Inner>{children}</Inner> </Container> ); }

Los componentes Container e Inner del fragmento de código anterior son de nuestro archivo ./accordion.styles.js en el que creamos estilos para nuestros componentes usando los componentes con styled-components (de la biblioteca css-in-js ). El componente Container alberga todo el Accordion que estamos construyendo mediante el uso de componentes compuestos.

Aquí estamos creando un objeto de contexto utilizando el método createContext() , por lo que cuando React representa un componente que se suscribe a este objeto de contexto, leerá el valor de contexto actual del proveedor más cercano que coincida sobre él en el árbol.

Luego, también estamos creando nuestro componente base, que es el Acordeón; se lleva a los children y cualquier restProps . Este es nuestro componente principal que alberga los componentes secundarios del acordeón.

Vamos a crear otros componentes secundarios dentro del archivo accordion.js :

 Accordion.Title = function AccordionTitle({ children, ...restProps }) { return <Title {...restProps}>{children}</Title>; }; Accordion.Frame = function AccordionFrame({ children, ...restProps }) { return <Frame {...restProps}>{children}</Frame>; };

Note el . después del componente principal Accordion; esto se usa para conectar el componente secundario a su componente principal.

Continuemos. Ahora agregue lo siguiente al archivo 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> ); };

Así que aquí estamos creando un componente de Body , Header y Item que son todos hijos del componente principal Accordion . Aquí es donde podría empezar a complicarse. Además, tenga en cuenta que cada componente secundario creado aquí también recibe un restprops children

Desde el componente secundario Item , inicializamos nuestro estado usando el gancho useState y lo configuramos como verdadero. Luego, recuerde también que creamos un ToggleContext en el nivel superior del archivo accordion.js , que es un Context Object , y cuando React representa un componente que se suscribe a este objeto de contexto, leerá el valor de contexto actual del proveedor más cercano que se encuentre arriba. en el árbol.

Cada objeto Context viene con un componente Provider React que permite que los componentes consumidores se suscriban a los cambios de contexto.

El componente del provider acepta que se pase una propiedad de value a los componentes de consumo que son descendientes de este proveedor, y aquí estamos pasando el valor del estado actual, que es toggleShow y el método para establecer el valor del estado actual setToggleShow . Son el valor que determina cómo nuestro objeto de contexto compartirá el estado alrededor de nuestro componente sin perforación de prop.

Luego, en nuestro componente secundario de header del Accordion , estamos destruyendo los valores del objeto de contexto, luego cambiando el estado actual de toggleShow al hacer clic. Entonces, lo que estamos tratando de hacer es ocultar o mostrar nuestro acordeón cuando se hace clic en el encabezado.

En nuestro componente Accordion.Body , también estamos destruyendo el toggleShow que es el estado actual del componente, luego, según el valor de toggleShow , podemos ocultar el cuerpo o mostrar el contenido del componente Accordion.Body .

Eso es todo para nuestro archivo accordion.js .

Ahora aquí es donde podemos ver cómo se une todo lo que hemos aprendido sobre los componentes Context y Compound components . Pero antes de eso, creemos un nuevo archivo llamado data.json y peguemos el siguiente contenido en él:

 [ { "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." } ]

Estos son los datos con los que trabajaremos para probar nuestro componente de acordeón.

Así que sigamos. Casi hemos terminado y creo que has aprendido mucho al seguir este artículo.

En esta sección, vamos a reunir todo lo que hemos estado trabajando y aprendiendo sobre componentes compuestos para poder usarlo en nuestro archivo App.js para usar la función Array.map para mostrar los datos que ya tenemos en la web. página. También tenga en cuenta que no se usó el estado dentro de App.js ; todo lo que hicimos fue pasar datos a los componentes específicos y la API de contexto se encargó de todo lo demás.

Ahora vamos a la parte final. En su App.js , haga lo siguiente:

 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> ); }

En su archivo App.js , importamos nuestro acordeón de componentes compuestos desde la ruta del archivo, luego también importamos nuestros datos ficticios, mapeamos a través de los datos ficticios para obtener los elementos individuales en nuestro archivo de datos, luego los mostramos de acuerdo con el respectivo componente, también notará que todo lo que tuvimos que hacer fue pasar los elementos secundarios al componente respectivo, la API de contexto se encarga de garantizar que llegue al componente correcto y no hubo perforación de accesorios.

Así es como debería verse nuestro producto final:

Aspecto final de nuestro componente de acordeón
Aspecto final de nuestro componente de acordeón. (Vista previa grande)

Alternativa a los componentes compuestos

Una alternativa al uso de componentes compuestos sería utilizar la API Render Props. El término Render Prop en React se refiere a una técnica para compartir código entre componentes de React utilizando un accesorio cuyo valor es una función. Un componente con un accesorio de renderizado toma una función que devuelve un elemento React y lo llama en lugar de implementar su propia lógica de renderizado.

Pasar datos de un componente a un componente secundario que necesita los datos puede resultar en la exploración de apoyo cuando tiene componentes anidados entre sí. Esta es la ventaja de usar Context para compartir datos entre componentes sobre el uso del método render prop.

Conclusión

En este artículo, aprendimos sobre uno de los patrones avanzados de React, que es el patrón de componente compuesto. Es un método increíble para construir componentes reutilizables en React usando el patrón de componente compuesto para construir su componente que le ofrece mucha flexibilidad en su componente. Todavía puede optar por hacer uso de Render Prop si la flexibilidad no es lo que su componente requiere en este momento.

Los componentes compuestos son más útiles en los sistemas de diseño de edificios. También pasamos por el proceso de compartir el estado dentro de los componentes utilizando la API de contexto.

  • El código de este tutorial se puede encontrar en Codesandbox.