Menu

Prop children w React: komponenty opakowujące i sloty

children to prop, który przechowuje wszystko, co umieścisz między tagiem otwierającym i zamykającym komponentu. Dowiedz się, jak budować z nim komponenty opakowujące i układy, przekazywać kilka slotów jako nazwane propsy, używać children jako funkcji i kiedy API Children to złe narzędzie.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

W React children to prop, który przechowuje wszystko, co napiszesz między tagiem otwierającym i zamykającym komponentu. Komponent, który renderuje gdzieś w swoim wyniku {children}, staje się opakowaniem: dostarcza ramę, a wywołujący dostarcza zawartość.

Obie karty mają wspólną ramkę, padding i styl nagłówka, ale każda zawiera zupełnie inną treść. Dodaj trzecią <Card title="Help"> z linkiem w środku, a dostanie tę samą ramę za darmo.

Jak children trafia do komponentu

JSX zamienia zagnieżdżoną zawartość w prop. Te dwie linie dają ten sam element:

<Card title="Profile"><p>Ada</p></Card>

<Card title="Profile" children={<p>Ada</p>} />

children to więc zwykły prop ze specjalnym sposobem przekazywania. Destrukturyzujesz go jak każdy inny (function Card({ children })) albo odczytujesz jako props.children. Strona o propsach omawia propsy ogólnie.

To, co trafia do children, zależy od tego, co napisałeś między tagami. Jedno dziecko przychodzi jako to dziecko. Kilkoro dzieci przychodzi jako tablica. Tekst przychodzi jako string, a brak czegokolwiek jako undefined.

Konsola pokazuje object, array of 2, string i undefined. Rzadko musisz się tym przejmować: wyrenderowanie {children} obsługuje wszystkie te przypadki, a null, undefined, true i false nie renderują niczego. Kształt ma znaczenie tylko wtedy, gdy próbujesz badać albo zmieniać children, przed czym ostrzega ostatnia sekcja.

Komponenty układu

Najczęstsze użycie children to komponent układu: szkielet strony, układ z panelem bocznym, wyśrodkowana kolumna. Komponent odpowiada za strukturę i odstępy, a każda strona, która go używa, przekazuje własną zawartość.

function PageLayout({ children }) {
    return (
        <div className="page">
            <Header />
            <main className="page-content">{children}</main>
            <Footer />
        </div>
    );
}

function AboutPage() {
    return (
        <PageLayout>
            <h1>About us</h1>
            <p>We teach people to code.</p>
        </PageLayout>
    );
}

PageLayout nie wie i nie obchodzi go, co zawiera strona „o nas". O to właśnie chodzi: zmień nagłówek raz, a każda strona owinięta w ten układ to przejmie.

Kilka slotów z nazwanymi propsami

children to jeden slot. Gdy komponent potrzebuje zawartości w więcej niż jednym miejscu, przekaż dodatkowe części jako nazwane propsy. Każdy prop może przechowywać JSX, więc modal może przyjąć title, footer i swoją treść jako children.

Szkielet modala kontroluje ramki, padding i kolejność trzech obszarów, a wywołujący wypełnia każdy z nich. Inne frameworki nazywają je slotami; w React to po prostu propsy. <>...</> wokół dwóch przycisków to fragment, który grupuje je bez dodawania dodatkowego elementu. Prawdziwe okno dialogowe renderowałoby się też do document.body przez portal, żeby leżało nad stroną.

Przenieś <p> do propsa title, a tekst treści pojawi się w nagłówku: komponent decyduje, gdzie trafia każdy prop, a wywołujący decyduje tylko, co się w nim znajdzie.

Children jako funkcja

Czasem opakowanie ma jakiś stan, a wywołujący powinien zdecydować, jak go narysować. Zamiast elementu wywołujący przekazuje funkcję jako children, a opakowanie wywołuje ją z wartościami. Ten wzorzec nazywa się render prop.

Jeden Toggle rysuje przycisk, a drugi pole wyboru, z tej samej logiki. Render propsy były głównym sposobem współdzielenia logiki ze stanem przed hookami. Dziś własny hook taki jak useToggle() zwykle czyta się lepiej, ale render propsy wciąż zobaczysz w bibliotekach do list, formularzy i animacji.

API Children i cloneElement

React eksportuje obiekt Children z pomocnikami do przechodzenia po children: Children.map, Children.forEach, Children.count, Children.toArray i Children.only. Razem z cloneElement, które kopiuje element z nowymi propsami, pozwalają rodzicowi badać i zmieniać to, co dostał. Zespół Reacta wymienia je jako przestarzałe API: wciąż działają, ale nowy kod powinien ich unikać.

Powód jest taki, że widzą one tylko elementy zapisane bezpośrednio między tagami. Nie widzą tego, co te elementy renderują.

Lista pokazuje cztery elementy, ale konsola mówi Children.count = 3, bo TwoMore to jedno dziecko bez względu na to, ile elementów renderuje. cloneElement przekazuje też prop style do TwoMore, który go ignoruje, więc trzeci i czwarty element nie dostają pasków. Każdy, kto przerobi kilka elementów <li> na komponent, psuje rodzica, nawet go nie dotykając.

Rozwiązanie to przestać sięgać do children i przekazywać dane:

function NumberedList({ items }) {
    return (
        <ol>
            {items.map((item, i) => (
                <li key={item.id} style={{ color: i % 2 ? 'gray' : 'black' }}>
                    {item.label}
                </li>
            ))}
        </ol>
    );
}

Teraz lista sama renderuje każdy wiersz i nic nie zależy od tego, jak wywołujący napisał swój JSX. Gdy rodzic musi współdzielić wartości z głęboko zagnieżdżonymi dziećmi (wybraną kartę, motyw), użyj kontekstu zamiast klonowania na nie propsów.

Typowanie children w TypeScripcie

W TypeScripcie nadaj children typ React.ReactNode. Obejmuje on wszystko, co React potrafi wyrenderować: elementy, teksty, liczby, tablice takich wartości, null, undefined i wartości logiczne.

import type { ReactNode } from 'react';

type CardProps = {
    title: string;
    children: ReactNode;
};

function Card({ title, children }: CardProps) {
    return (
        <section>
            <h3>{title}</h3>
            {children}
        </section>
    );
}

Render propowi nadaj typ funkcji, którą jest: children: (on: boolean, toggle: () => void) => ReactNode. Uczyń children opcjonalnym (children?: ReactNode), gdy komponent ma sens także pusty.

Częste błędy

Zapomnienie o wyrenderowaniu children. Jeśli komponent przyjmuje children, ale nigdy nie umieszcza {children} w swoim wyniku, zawartość po cichu znika. Nic cię nie ostrzega.

Wywoływanie children jak funkcji, gdy nią nie jest. children() działa tylko wtedy, gdy wywołujący przekazał funkcję. Jeśli komponent oczekuje render propa, zaznacz to w jego nazwie albo dokumentacji, albo przyjmij nazwany prop taki jak render, żeby intencja była jasna.

Modyfikowanie children. Elementy są tylko do odczytu. W trybie deweloperskim React je zamraża, więc przypisanie do children.props rzuca tam błąd. Zamiast tego buduj nowy wynik.

Najczęściej zadawane pytania

Co to jest props.children w React?

To zawartość zapisana między tagiem otwierającym i zamykającym komponentu. W <Card><p>Hi</p></Card> funkcja Card dostaje <p> jako props.children i decyduje, gdzie go wyrenderować.

Czy children to specjalny prop?

Tylko pod względem sposobu przekazywania. JSX umieszcza zagnieżdżoną zawartość w propsie o nazwie children, ale wewnątrz komponentu to zwykły prop. Możesz go też przekazać jawnie, jak w <Card children={<p>Hi</p>} />, i działa tak samo.

Jak przekazać do komponentu więcej niż jeden blok zawartości?

Użyj nazwanych propsów dla dodatkowych bloków. Prop może przechowywać JSX tak samo jak children, więc <Modal title={<h2>Delete?</h2>} footer={<button>OK</button>}>Body</Modal> daje komponentowi trzy sloty.

Czy używać Children.map i cloneElement?

Unikaj ich w nowym kodzie. Widzą tylko elementy zapisane bezpośrednio między tagami, a nie to, co te elementy renderują, więc psują się, gdy tylko ktoś owinie dziecko w inny komponent. Zamiast tego przekaż tablicę danych jako prop albo użyj kontekstu.

Jaki typ ma children w TypeScripcie?

Użyj React.ReactNode. Obejmuje wszystko, co React potrafi wyrenderować: elementy, teksty, liczby, tablice, null, undefined i wartości logiczne.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ