- Published on
Jak uniknąć przekazywania wielu propsów do komponentów za pomocą kompozycji komponentów w React
- Authors
- Name
- Marcin Parda
Problem
Wyobraź sobie, że musisz zaimplementować w swojej aplikacji prosty panel, który wyświetli użytkownikowi informację o pomyślnie wykonanej akcji. Wystarczy, że przekażesz kilka propsów do komponentu odpowiedzialnego za wyświetlanie tego panelu. Wszystko wskazuje na to, że możesz to zrobić w 5 minut.
Zaczynasz nad tym pracować, ale kiedy zaczynasz implementować ten komponent, zdajesz sobie sprawę, że musisz przekazać kilka propsów związanych z tym, jak wygląda nagłówek panelu.
Następnie zauważasz, że musisz przekazać kilka propsów związanych z tym, jak wygląda treść w tym panelu.
Później zauważasz, że musisz przekazać propsy odpowiedzialne za to, jak wyglądają przyciski zwijania i zamykania panelu oraz czy w ogóle je pokazywać.
I tak dalej.
W rezultacie nasz "mały" potwór wygląda tak:
<Panel
headerTitle="Panel title"
headerIcon="icon"
headerIconColor="red"
headerTextColor="white"
headerButtonCloseColor="white"
headerButtonCloseVisible={true}
headerButtonCloseOnClick={() => {}}
headerButtonCollapseColor="white"
headerButtonCollapseVisible={true}
headerButtonCollapseOnClick={() => {}}
contentBackgroundColor="white"
contentTextColor="black"
contentText="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec auctor, nisl eget ultricies ultricies, nunc
tortor aliquam nisl, eget ultricies lorem ipsum vitae nunc. Donec auctor, nisl eget ultricies ultricies, nunc tortor aliquam
nisl, eget ultricies lorem ipsum vitae nunc."
contentButtonColor="blue"
...
/>
Gdy już zaimplementujesz swój komponent, czas przejść do następnego komponentu do zaimplementowania, którym będzie panel wyświetlający użytkownikowi błąd akcji. W tym momencie masz dwie opcje:
- Zaimplementuj nowy komponent tak samo jak poprzedni, ale o nieco innym wyglądzie i nazwie.
- Dodaj propsy do komponentu
Panel
, które będą odpowiedzialne za wyświetlanie błędu. W rezultacie liczba propsów może się nawet podwoić :(
...albo możesz dowiedzieć się o kompozycji komponentów i compound components i zaimplementować swój komponent w taki sposób, aby nie musieć przekazywać do niego dziesiątek propsów. W tym artykule omówimy pierwszą opcję.
Rozwiązanie
Kompozycja komponentów
Zacznijmy od techniki kompozycji komponentów. Jest to technika, która pozwala tworzyć bardziej złożone komponenty poprzez komponowanie mniejszych, logicznych podkomponentów.
Możesz znać tag HTML <table>
. Wewnątrz tego tagu możesz umieścić tagi <tr>
i <td>
, które są odpowiedzialne za wyświetlanie wierszy i kolumn w tabeli.
Wszystkie te tagi są ze sobą powiązane, a ich wspólnym rodzicem jest tag <table>
. W ten sposób możesz tworzyć bardziej złożone komponenty, komponując mniejsze, logiczne podkomponenty.
W podobny sposób możesz tworzyć bardziej złożone komponenty w React, komponując mniejsze, logiczne podkomponenty.
Jeśli korzystałeś z biblioteki Material UI, musisz znać komponenty takie jak Button
, TextField
lub Select
.
Ich użycie może wyglądać tak:
<Select
labelId="demo-simple-select-label"
id="demo-simple-select"
value={age}
label="Age"
onChange={handleChange}
>
<MenuItem value={10}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
<MenuItem value={30}>Thirty</MenuItem>
</Select>
Jak widać w powyższym przykładzie, Select
przyjmuje swoje dzieci w postaci kilku MenuItem
, zamiast przekazywać te elementy w propsach.
Dzięki temu elementy te można modyfikować niezależnie od komponentu Select
. Ale jak możemy zaimplementować nasz Panel
w taki sposób?
Implementacja kompozycji komponentów
- Najpierw zdefiniujmy interfejs dla naszego (nieco uproszczonego) komponentu
Panel
:
// Panel.tsx
interface PanelProps {
title: string
}
// types.ts
interface ParentProps {
children: React.ReactNode
}
- Następnie zdefiniujmy komponent
Panel
:
// Panel.tsx
export const Panel = ({ title, children }: PanelProps & ParentProps) => {
return (
<div>
<h1>{title}</h1>
{children}
</div>
)
}
- Teraz zdefiniujmy komponenty
PanelHeader
iPanelBody
:
// PanelHeader.tsx
export const PanelHeader = ({ children }: ParentProps) => {
return <header>{children}</header>
}
// PanelBody.tsx
export const PanelBody = ({ children }: ParentProps) => {
return <main>{children}</main>
}
- Całość będzie wyglądać tak:
Panel.tsx
import { ParentProps } from './types'
interface PanelProps {
title: string
}
export const Panel = ({ title, children }: PanelProps & ParentProps) => {
return (
<div>
<h1>{title}</h1>
{children}
</div>
)
}
PanelHeader.tsx
import { ParentProps } from './types'
export const PanelHeader = ({ children }: ParentProps) => {
return <header>{children}</header>
}
PanelBody.tsx
import { ParentProps } from './types'
export const PanelBody = ({ children }: ParentProps) => {
return <main>{children}</main>
}
Dzięki temu możemy używać tych komponentów w następujący sposób:
<Panel title="Panel">
<PanelHeader>
<h3>Header</h3>
</PanelHeader>
<PanelBody>
<p>Body</p>
</PanelBody>
</Panel>
Dzięki temu osiągnęliśmy kilka rzeczy:
PanelHeader
iPanelBody
są komponentami potomnymiPanel
, możemy je łatwo modyfikować bez modyfikowaniaPanel
, unikając props drilling.- Usunęliśmy z
Panel
odpowiedzialność za renderowaniePanelHeader
iPanelBody
. Gdyby którykolwiek z tych komponentów miał swój własny stan, renderowanie go nie spowodowałoby ponownego renderowania dużego komponentuPanel
. - Łatwo uzyskaliśmy możliwość modyfikacji komponentu
Panel
. Nic nie stoi na przeszkodzie, aby zamienićPanelHeader
iPanelBody
w miejscu lub usunąć jeden z nich.
Ale dodaliśmy też kilka problemów:
- Dodaliśmy nową warstwę abstrakcji, o której musisz pamiętać i wiedzieć, że istnieje. Przydałoby się też ją udokumentować.
- Nie wiemy dokładnie, jakie komponenty powinniśmy użyć w
Panel
, jeśli chcemy, aby cały komponent wyglądał spójnie i działał poprawnie. Przydałoby się mieć rodzaj podpowiedzi składni, która powiedziałaby nam, co powinniśmy użyć wPanel
, prawda? :) - Gdybyśmy chcieli, aby komponenty mogły ze sobą komunikować się, np. gdy klikniemy przycisk w
PanelHeader
chcielibyśmy ukryć cały komponentPanel
, to musielibyśmy przekazać dodatkowe propsy do komponentów (handleClick
,isPanelVisible
).
Rozwiązaniem ostatnich dwóch (a częściowo pierwszego) problemów jest użycie compound components z Context API.
W następnym artykule opiszę, jak to zrobić.
Podsumowanie
To wszystko. Mam nadzieję, że podobał Ci się ten artykuł. Jeśli masz jakieś pytania, śmiało zadaj je w komentarzach.
Oto dodatek dla Ciebie. Krótkie podsumowanie artykułu. Możesz go wykorzystać do tworzenia kart fiches (np. w Anki).
Czym jest kompozycja komponentów?
Kompozycja komponentów to technika, która pozwala tworzyć bardziej złożone komponenty poprzez komponowanie mniejszych, logicznych podkomponentów.
Przykład:
<Panel title="Panel">
<PanelHeader>
<h3>Header</h3>
</PanelHeader>
<PanelBody>
<p>Body</p>
</PanelBody>
</Panel>
Widzisz błąd / literówkę w artykule? Zgłoś poprawkę lub dodaj komentarz na dole.
Zobacz post na GitHubie