Составные компоненты в React
Опубликовано: 2022-03-10Составные компоненты помогают разработчикам создавать более выразительные и гибкие API для обмена состоянием и логикой внутри компонентов. В этом руководстве объясняется, как этого можно добиться с помощью Context API и React для создания компонентов с помощью этого расширенного шаблона.
Примечание . Чтобы продолжить, вам необходимо иметь общее представление о React и о том, как работает Context API.
Что такое составной компонент?
Можно сказать, что составные компоненты представляют собой шаблон, который включает в себя состояние и поведение группы компонентов, но при этом возвращает внешнему пользователю управление отрисовкой его переменных частей.
Из приведенного выше определения, принимая во внимание ключевые слова: состояние и поведение . Это помогает нам понять, что составной компонент имеет дело с состоянием (т. е. как состояние ведет себя в компоненте, который заключен внешним пользователем, являющимся родителем компонента).
Цель составных компонентов — предоставить более выразительный и гибкий API для связи между родительскими и дочерними компонентами.
Думайте об этом как о тегах <select>
и <option>
в HTML:
<select> <option value="volvo">Volvo</option> <option value="mercedes">Mercedes</option> <option value="audi">Audi</option> </select>
Тег select
работает вместе с тегом option
, который используется для раскрывающегося меню для выбора элементов в HTML. Здесь <select>
управляет состоянием пользовательского интерфейса, затем элементы <option>
настраиваются на то, как должен работать <select>
. Составные компоненты в React используются для создания декларативного компонента пользовательского интерфейса, который помогает избежать детализации реквизита.
Детализация реквизита — это передача реквизита по нескольким дочерним компонентам. Это также то, что они называют «запахом кода». Худшая часть пропеллерного сверления заключается в том, что при повторном рендеринге родительского компонента дочерние компоненты также будут повторно рендериться и вызовут эффект домино для компонента. Хорошим решением было бы использовать React Context API, который мы также рассмотрим позже.
Применение составных компонентов в React
В этом разделе объясняются пакеты, которые мы можем использовать в нашем приложении, которые используют шаблон составных компонентов для создания компонентов в React. Этот пример представляет собой компонент Menu
из пакета пользовательского интерфейса @reach
.
import { Menu, MenuList, MenuButton, MenuItem, MenuItems, MenuPopover, MenuLink, } from "@reach/menu-button"; import "@reach/menu-button/styles.css";
Вот как вы можете использовать компонент Menu
:
function Example() { return ( <Menu> <MenuButton>Actions</MenuButton> <MenuList> <MenuItem>Download</MenuItem> <MenuLink to="view">View</MenuLink> </MenuList> </Menu> ); }
Приведенный выше пример кода является одной из реализаций составных компонентов, в которой вы видите, что Menu
, MenuButton
, MenuList
, MenuItem
и MenuLink
были импортированы из @reach/menu-button
. В отличие от экспорта одного компонента, ReachUI экспортирует родительский компонент, который представляет собой Menu
, сопровождающий его дочерние компоненты, такие как MenuButton
, MenuList
, MenuItem
и MenuLink
.
Когда следует использовать составные компоненты?
Как разработчик React, вы должны использовать составные компоненты, когда хотите:
- Решить вопросы, связанные с созданием повторно используемых компонентов;
- Разработка высокосвязных компонентов с минимальной связью;
- Улучшенные способы совместного использования логики между компонентами.
Плюсы и минусы составных компонентов
Составной компонент — это отличный шаблон React, который можно добавить в набор инструментов разработчика React. В этом разделе я расскажу о плюсах и минусах использования составных компонентов и о том, чему научился при создании компонентов с использованием этого шаблона разработки.
Плюсы
Разделение ответственности
Наличие всей логики состояния пользовательского интерфейса в родительском компоненте и передача ее внутри всех дочерних компонентов обеспечивает четкое разделение ответственности.Уменьшенная сложность
В отличие от детализации реквизита для передачи свойств их конкретным компонентам, дочерние реквизиты переходят к соответствующим дочерним компонентам с использованием шаблона составного компонента.
Минусы
Одним из основных недостатков создания компонентов в React с использованием шаблона составного компонента является то, что только direct children
элементы родительского компонента будут иметь доступ к свойствам, а это означает, что мы не можем обернуть какой-либо из этих компонентов в другой компонент.
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> ); }
Решением этой проблемы может быть использование шаблона гибкого составного компонента для неявного совместного использования состояния с помощью API React.createContext
.
Context API позволяет передавать состояние React через вложенные компоненты при сборке с использованием шаблона составных компонентов для создания компонентов в React. Это возможно, потому что context
предоставляет способ передачи данных вниз по дереву компонентов без необходимости вручную передавать реквизиты на каждом уровне. Использование Context API предоставляет конечному пользователю большую гибкость.
Поддержка составных компонентов в React
Составные компоненты обеспечивают более гибкий способ совместного использования состояния в приложениях React, поэтому использование составных компонентов в ваших приложениях React упрощает поддержку и фактическую отладку ваших приложений.
Создание демонстрации
В этой статье мы собираемся создать компонент аккордеона в React, используя шаблон составных компонентов. Компонент, который мы собираемся создать в этом руководстве, будет настраиваемым компонентом-аккордеоном , который является гибким и разделяет состояние внутри компонента с помощью Context API.
Поехали!
Прежде всего, давайте создадим приложение React, используя следующее:
npx create-react-app accordionComponent cd accordionComponent npm start
или
yarn create react-app accordionComponent cd accordionComponent yarn start
Приведенные выше команды создают приложение React, изменяют каталог на проект React и запускают сервер разработки.
Примечание . В этом уроке мы будем использовать styled-components
, чтобы помочь стилизовать наши компоненты.
Используйте приведенную ниже команду для установки styled-components
:
yarn add styled-components
или
npm install --save styled-components
В папке src создайте новую папку с именем components . Здесь будут жить все наши компоненты. В папке компонентов создайте два новых файла: accordion.js
и accordion.styles.js
.
Файл accordion.styles.js
содержит наши стили для компонента Accordion
(наши стили были выполнены с использованием styled-components
).
import styled from "styled-components"; export const Container = styled.div` display: flex; border-bottom: 8px solid #222; `;
Выше приведен пример стилизации компонентов с помощью библиотеки css-in-js
под названием styled-components
.
В файле accordion.styles.js
добавьте оставшиеся стили:
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; } `;
Давайте начнем создавать наш компонент аккордеона. В файл accordion.js
добавим следующий код:
import React, { useState, useContext, createContext } from "react"; import { Container, Inner, Item, Body, Frame, Title, Header } from "./accordion.styles";
Выше мы импортируем useState
, useContext
и createContext
, которые помогут нам построить наш компонент аккордеона, используя составные компоненты.
В документации React объясняется, что context
помогает передавать данные через дерево компонентов без необходимости вручную передавать свойства на каждом уровне.
Глядя на то, что мы импортировали ранее в наш accordion.js
, вы заметите, что мы также импортировали наши стили как компоненты, которые помогут нам быстрее создавать наши компоненты.
Мы продолжим и создадим наш контекст для компонента, который будет делиться данными с компонентами, которым они нужны:
const ToggleContext = createContext(); export default function Accordion({ children, ...restProps }) { return ( <Container {...restProps}> <Inner>{children}</Inner> </Container> ); }
Компоненты Container
и Inner
из приведенного выше фрагмента кода взяты из нашего файла ./accordion.styles.js
, в котором мы создали стили для наших компонентов с помощью styled-components
(из библиотеки css-in-js
). Компонент Container
содержит весь Accordion
, который мы строим, используя составные компоненты.
Здесь мы создаем объект контекста с помощью createContext()
, поэтому, когда React отображает компонент, который подписывается на этот объект Context, он будет считывать текущее значение контекста из ближайшего соответствующего провайдера над ним в дереве.
Затем мы также создаем наш базовый компонент — аккордеон; он принимает children
и любой restProps
. Это наш родительский компонент, в котором находятся дочерние компоненты Аккордеона.
Давайте создадим другие дочерние компоненты в файле accordion.js
:
Accordion.Title = function AccordionTitle({ children, ...restProps }) { return <Title {...restProps}>{children}</Title>; }; Accordion.Frame = function AccordionFrame({ children, ...restProps }) { return <Frame {...restProps}>{children}</Frame>; };
Обратите внимание на .
после родительского компонента Accordion; это используется для подключения дочернего компонента к его родительскому компоненту.
Давай продолжим. Теперь добавьте в файл 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> ); };
Итак, здесь мы создаем компоненты Body
, Header
и Item
, которые являются дочерними элементами родительского компонента Accordion
. Здесь могут начаться сложности. Также обратите внимание, что каждый созданный здесь дочерний компонент также получает children
свойства и restprops
.
Из дочернего компонента Item
мы инициализировали наше состояние с помощью хука useState
и установили его в значение true. Затем также помните, что мы создали ToggleContext
на верхнем уровне accordion.js
, который является Context Object
, и когда React отображает компонент, который подписывается на этот объект Context, он будет считывать текущее значение контекста из ближайшего соответствующего Provider над ним. в дереве.
Каждый объект Context поставляется с компонентом Provider
React, который позволяет потребляющим компонентам подписываться на изменения контекста.
Компонент provider
принимает свойство value
для передачи потребляющим компонентам, которые являются потомками этого провайдера, и здесь мы передаем текущее значение состояния, которое является toggleShow
и метод для установки значения текущего состояния setToggleShow
. Это значение, которое определяет, как наш объект контекста будет делиться состоянием вокруг нашего компонента без сверления реквизита.
Затем в нашем дочернем компоненте header
Accordion
мы уничтожаем значения объекта контекста, а затем меняем текущее состояние toggleShow
при нажатии. Итак, что мы пытаемся сделать, так это скрыть или показать наш аккордеон при нажатии на заголовок.
В нашем компоненте Accordion.Body
мы также уничтожаем toggleShow
, который является текущим состоянием компонента, затем, в зависимости от значения toggleShow
, мы можем либо скрыть тело, либо показать содержимое компонента Accordion.Body
.
Вот и все, что касается нашего accordion.js
.
Теперь мы можем увидеть, как все, что мы узнали о компонентах Context
и Compound components
, объединяется. Но перед этим давайте создадим новый файл с именем data.json
и вставим в него содержимое ниже:
[ { "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." } ]
Это данные, с которыми мы будем работать, чтобы протестировать наш компонент аккордеона.
Итак, давайте продолжим. Мы почти закончили, и я полагаю, что вы многому научились, прочитав эту статью.
В этом разделе мы собираемся собрать воедино все, над чем мы работали и узнавали о составных компонентах, чтобы иметь возможность использовать это в нашем файле App.js
, чтобы использовать функцию Array.map
для отображения данных, которые у нас уже есть в Интернете. страница. Также обратите внимание, что в App.js
не используется состояние; все, что мы делали, это передавали данные определенным компонентам, а Context API позаботился обо всем остальном.
Теперь о заключительной части. В вашем App.js
сделайте следующее:
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> ); }
В вашем файле App.js мы импортировали аккордеон составных компонентов из пути к файлу, а затем также импортировали наши фиктивные данные, сопоставленные с фиктивными данными, чтобы получить отдельные элементы в нашем файле данных, а затем отобразили их в соответствии с соответствующим компонент, также вы могли бы заметить, что все, что нам нужно было сделать, это передать дочерние элементы соответствующему компоненту, API контекста позаботится о том, чтобы он достиг нужного компонента, и не было сверления реквизита.
Вот как должен выглядеть наш конечный продукт:
Альтернатива составным компонентам
Альтернативой использованию составных компонентов может быть использование Render Props API. Термин Render Prop в React относится к методу совместного использования кода между компонентами React с использованием свойства, значением которого является функция. Компонент с параметром рендеринга принимает функцию, которая возвращает элемент React, и вызывает ее вместо реализации собственной логики рендеринга.
Передача данных от компонента вниз к дочернему компоненту, которому нужны данные, может привести к детализации поддержки, когда у вас есть компоненты, вложенные друг в друга. В этом преимущество использования Context для обмена данными между компонентами по сравнению с использованием метода render prop.
Заключение
В этой статье мы узнали об одном из продвинутых шаблонов React, который представляет собой шаблон составного компонента. Это отличный метод создания повторно используемых компонентов в React с использованием шаблона составного компонента для создания вашего компонента, который предлагает вам большую гибкость в вашем компоненте. Вы по-прежнему можете использовать Render Prop, если гибкость — это не то, что требуется вашему компоненту в данный момент.
Составные компоненты наиболее полезны при построении систем проектирования. Мы также прошли через процесс обмена состоянием внутри компонентов с помощью Context API.
- Код для этого руководства можно найти на Codesandbox.