- Published on
Implementacja compound components przy użyciu React Context
- Authors
- Name
- Marcin Parda
Problem
W poprzednim artykule pokazałem jak uniknąć przekazywania dziesiątek propsów do komponentów przy użyciu wzorca Compound Components. Jednakże napotkaliśmy kilka problemów:
- Dodaliśmy nową warstwę abstrakcji, której trzeba pamiętać i wiedzieć, że istnieje. Przydałoby się ją również udokumentować.
- Nie wiemy dokładnie jakie komponenty powinniśmy użyć w
Panel
jeśli chcemy, żeby cały komponent wyglądał konsekwentnie i działał poprawnie. Przydałoby się jakieś podpowiedzenie składni, które powiedziałoby nam co powinniśmy użyć wPanel
, prawda? :)- Jeśli chcielibyśmy, żeby komponenty mogły ze sobą komunikować się, np. po kliknięciu przycisku w
PanelHeader
chcielibyśmy ukryć cały komponentPanel
, to musielibyśmy przekazywać dodatkowe propsy do komponentów (handleClick
,isPanelVisible
).
Rozwiązanie
Compound components przy użyciu React Context
Compound component to wzorzec do tworzenia wielokrotnego użytku komponentów, które składają się z wielu mniejszych komponentów.
W compound component, główny komponent nadrzędny dostarcza zestaw komponentów podrzędnych, które współpracują ze sobą w celu wdrożenia ogólnej funkcjonalności komponentu nadrzędnego.
W poprzednim artykule stworzyliśmy komponent Panel
, który wyglądał tak:
<Panel title="Panel">
<PanelHeader>
<h3>Header</h3>
</PanelHeader>
<PanelBody>
<p>Body</p>
</PanelBody>
</Panel>
Przede wszystkim naprawmy ten problem:
- Nie wiemy dokładnie jakie komponenty powinniśmy użyć w
Panel
jeśli chcemy, żeby cały komponent wyglądał konsekwentnie i działał poprawnie. Przydałoby się jakieś podpowiedzenie składni, które powiedziałoby nam co powinniśmy użyć wPanel
, prawda? :)
Aby to zrobić wystarczy, że będziemy stosować taką składnię w naszym komponencie Panel
:
Panel.Header = function PanelHeader({ children }: ParentProps) {
return <header>{children}</header>
}
Dzięki temu w naszym edytorze, gdy wpiszemy Panel.
i naciśniemy CTRL + Spacja
, powinniśmy dostać listę wszystkich komponentów, które możemy użyć w Panel
. Co pozwala nam używać go w taki sposób:
<Panel title="Panel">
<Panel.Header>
<h3>Header</h3>
</Panel.Header>
<Panel.Body>
<p>Body</p>
</Panel.Body>
</Panel>
Rozwiązuje to nasz pierwszy problem. Poza tym ten kod lepiej się dokumentuje. W jednym pliku mamy listę komponentów podrzędnych, które powinniśmy używać wewnątrz znaczników <Panel>
. Przejdźmy do drugiego problemu:
- Jeśli chcielibyśmy, żeby komponenty mogły ze sobą komunikować się, np. po kliknięciu przycisku w
PanelHeader
chcielibyśmy ukryć cały komponentPanel
, to musielibyśmy przekazywać dodatkowe propsy do komponentów (handleClick
,isPanelVisible
).
Jak to osiągnąć bez nadmiernego dodawania propsów. Na pomoc przyjdzie nam React Context. Jeśli jeszcze nie wiesz czym jest React Context, polecam przeczytać ten artykuł z nowej dokumentacji reacta.
Na początek zdefiniujemy PanelContext
dla naszego panelu:
interface PanelContextValue {
isVisible: boolean
showPanel: () => void
hidePanel: () => void
}
const PanelContext = React.createContext({} as PanelContextValue)
W tym kontekście będziemy przechowywać dane o tym czy Panel
jest widoczny oraz dwie funkcje, które będą zmieniały widoczność panelu.
Następną rzeczą do zrobienia będzie dodanie stanu i Provider
do komponentu nadrzędnego Panel
.
export const Panel = ({ title, children }: PanelProps & ParentProps) => {
const [isVisible, toggleVisibility] = React.useState(false)
const showPanel = () => toggleVisibility(true)
const hidePanel = () => toggleVisibility(false)
if (!isVisible) return <button onClick={showPanel}>Show Panel</button>
return (
<PanelContext.Provider value={{ isVisible, showPanel, hidePanel }}>
<div>
<h1>{title}</h1>
{children}
</div>
</PanelContext.Provider>
)
}
Jeśli panel nie jest widoczny zamiast tego pokażemy przycisk, który po kliknięciu pokaże panel. Ostatnią rzeczą będzie wykorzystanie wartości z Provider w komponentach podrzędnych.
Przepiszmy teraz Panel.Header
tak, aby mógł ukryć cały panel bez przekazywania propsów do tego komponentu:
Panel.Header = function Header({ children }: ParentProps) {
const { hidePanel } = useContext(PanelContext)
return (
<header>
{children}
<button onClick={hidePanel}>Hide Panel</button>
</header>
)
}
W ten sposób przekazujemy do nagłówka możliwość zmiany stanu komponentu Panel
, pozostawiając jako jedyny propsy children. W podobny sposób możemy przekazać odpowiednią funkcjonalność do innych podkomponentów.
Ostateczny plik Panel.tsx
wygląda teraz tak:
import React from 'react'
interface PanelProps {
title: string
}
interface ParentProps {
children: React.ReactNode
}
interface PanelContextValue {
isVisible: boolean
showPanel: () => void
hidePanel: () => void
}
const PanelContext = React.createContext({} as PanelContextValue)
export const Panel = ({ title, children }: PanelProps & ParentProps) => {
const [isVisible, toggleVisibility] = React.useState(false)
const showPanel = () => toggleVisibility(true)
const hidePanel = () => toggleVisibility(false)
if (!isVisible) return <button onClick={showPanel}>Show Panel</button>
return (
<PanelContext.Provider value={{ isVisible, showPanel, hidePanel }}>
<div>
<h1>{title}</h1>
{children}
</div>
</PanelContext.Provider>
)
}
Panel.Header = function Header({ children }: ParentProps) {
const { hidePanel } = React.useContext(PanelContext)
return (
<header>
{children}
<button onClick={hidePanel}>Hide Panel</button>
</header>
)
}
Panel.Body = function Body({ children }: ParentProps) {
return <main>{children}</main>
}
A jego użycie nie zmieniło się od ostatniego fragmentu kodu mimo, że dodaliśmy nową funkcjonalność:
<Panel title="Panel">
<Panel.Header>
<h3>Header</h3>
</Panel.Header>
<Panel.Body>
<p>Body</p>
</Panel.Body>
</Panel>
Podsumowanie
To wszystko. Mam nadzieję, że podobał Ci się ten artykuł. Jeśli masz jakieś pytania, śmiało zadawaj je w komentarzach.
Oto dodatek dla Ciebie. Krótkie podsumowanie artykułu. Możesz go wykorzystać do tworzenia fiszek (np. w Anki).
Co to jest compound component?
Compound component Compound component to komponent, który składa się z wielu komponentów podrzędnych. Komponenty podrzędne są zwykle łączone ze sobą w celu utworzenia bardziej złożonego komponentu, który wykonuje bardziej złożoną funkcję.
Przykład:
<Panel title="Panel">
<Panel.Header>
<h3>Header</h3>
</Panel.Header>
<Panel.Body>
<p>Body</p>
</Panel.Body>
</Panel>
Widzisz błąd / literówkę w artykule? Zgłoś poprawkę lub dodaj komentarz na dole.
Zobacz post na GitHubie