Menu

Prop drilling w React: co to jest i jak go unikać

Prop drilling to przekazywanie propsa przez komponenty, które go nie używają, tylko po to, żeby mógł go użyć komponent niżej. Zobacz to w działającym przykładzie, dowiedz się, dlaczego szkodzi, gdy aplikacja rośnie, i poznaj rozwiązania w kolejności, w jakiej warto je próbować: kompozycja, kontekst, a potem biblioteka stanu.

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

Prop drilling to przekazywanie propsa przez komponenty, które go nie używają, tylko po to, żeby mógł go użyć komponent niżej w drzewie. Tutaj user wędruje z App przez Page i Sidebar, żeby dotrzeć do Avatar, jedynego komponentu, który go czyta.

Cztery poziomy, a tylko ostatni interesuje się user. Konsola pokazuje, że Page i Sidebar wykonują się dla wartości, której nigdy nie wyświetlają. Kliknij przycisk, a znowu wypiszą log.

Dlaczego to szkodzi

Dwa albo trzy poziomy są w porządku. Prop drilling staje się problemem, gdy drzewo rośnie:

  • Komponenty pośrodku noszą propsy, których nie używają. Page i Sidebar mają teraz w sygnaturze prop user, więc nie da się ich czytać bez zastanawiania się, do czego służy.
  • Każda zmiana dotyka każdej warstwy. Dodaj user.avatarUrl albo drugą wartość, na przykład onLogout, a edytujesz każdy komponent na ścieżce, a nie tylko dwa końce.
  • Przeniesienie komponentu przerywa łańcuch. Przenieś Avatar do innej gałęzi, a będziesz musiał przeprowadzić user nową ścieżką.
  • Łatwo zgubić prop. Zapomnij przekazać go w jednej warstwie, a głęboki komponent dostanie undefined, bez żadnego błędu w warstwie, która go zgubiła.

Spróbuj: dodaj w App funkcję onLogout, którą ma wywołać Avatar. Edytujesz wszystkie cztery komponenty.

Rozwiązanie 1: kompozycja z children

To rozwiązanie ludzie pomijają. Zamiast przekazywać dane w dół, żeby głęboki komponent mógł się z nimi wyrenderować, pozwól komponentowi, który jest właścicielem danych, samemu wyrenderować głęboki komponent i przekaż gotowy element w dół jako children (albo dowolny inny prop). Warstwy pośrodku renderują slot i nigdy nie widzą user.

Page i Sidebar już nie wiedzą, że istnieje jakiś użytkownik. App tworzy Avatar tam, gdzie żyje user, i przekazuje go w dół jako gotowy element. Takie komponenty układu stają się wielokrotnego użytku, a dodanie onLogout to teraz zmiana tylko w App i Avatar. Oba nadal wypisują log przy każdym kliknięciu, bo App za każdym razem, gdy się renderuje, przekazuje im nowy element: kompozycja upraszcza kod, a nie zmniejsza liczbę renderowań. Strona o children omawia więcej wzorców ze slotami.

Kompozycja działa, gdy komponent, który jest właścicielem danych, może też zdecydować, jak wygląda głęboka część. Nie pomaga, gdy głęboki komponent siedzi w bibliotece albo w strukturze, której właściciel nie renderuje.

Rozwiązanie 2: kontekst

Gdy wiele komponentów na różnych głębokościach potrzebuje tej samej wartości (motyw, zalogowany użytkownik, bieżący język), umieść ją w kontekście. Każdy komponent pod providerem odczytuje ją przez useContext, a nic pomiędzy się nie zmienia.

Avatar i Greeting odczytują użytkownika z dwóch różnych głębokości, a Page i Sidebar nie przyjmują żadnych propsów. Dodaj trzeci komponent czytający gdziekolwiek pod Page, a zadziała bez ruszania reszty. Koszt: przepływ danych nie jest już widoczny w propsach, a każdy komponent czytający renderuje się, gdy zmienia się wartość. Strona o useContext omawia providery, wartości domyślne i te ponowne renderowania.

Rozwiązanie 3: biblioteka stanu

Dla dużego stanu aplikacji, który czyta wiele komponentów i który często się zmienia (koszyk, edytor dokumentów, dane na żywo), biblioteka stanu, taka jak Redux Toolkit, Zustand albo Jotai, pozwala każdemu komponentowi subskrybować tylko wycinek, którego używa. Pozwala to uniknąć zarówno drillingu, jak i kosztu „każdy konsument się renderuje" przy jednym dużym kontekście. To też kolejna zależność i kolejny zestaw pojęć, więc sięgaj po nią, gdy dwa pierwsze rozwiązania przestają wystarczać, a nie po to, żeby naprawić prop, który wędruje przez trzy poziomy.

// Zustand, for comparison (not available in the editor here)
const useCart = create((set) => ({
    items: [],
    add: (item) => set((s) => ({ items: [...s.items, item] })),
}));

function CartCount() {
    const count = useCart((s) => s.items.length); // renders only when the count changes
    return <span>{count}</span>;
}

Kiedy prop drilling jest w porządku

Przekazywanie propsów to sposób, w jaki płyną dane w React, i najłatwiejszy do czytania wzorzec: możesz prześledzić wartość od góry do dołu, patrząc na JSX. Prop, który przechodzi przez jeden czy dwa komponenty używające jego części albo przez krótki łańcuch blisko związanych komponentów, nie jest problemem do rozwiązania. Podnieś stan do najbliższego wspólnego rodzica, przekaż go w dół i zmieniaj podejście dopiero wtedy, gdy warstwy pośrodku zaczynają nosić propsy, do których nie mają żadnego zastosowania.

Najczęściej zadawane pytania

Co to jest prop drilling w React?

Przekazywanie propsa w dół przez kilka warstw komponentów, które go nie używają, tylko po to, żeby mógł go odczytać komponent głęboko w drzewie. Każdy komponent pośrodku przyjmuje prop i przekazuje go dalej.

Czy prop drilling jest zły?

Sam w sobie nie. Przekazanie wartości dwa albo trzy poziomy w dół jest jawne i łatwe do prześledzenia. Staje się problemem, gdy wiele warstw nosi propsy, których nigdy nie używa, więc każda zmiana nazwy albo nowe pole dotyka plików, które nie mają z tym nic wspólnego.

Jak uniknąć prop drillingu?

Najpierw spróbuj kompozycji: niech rodzic zbuduje głęboki komponent i przekaże go w dół jako children albo inny prop, żeby warstwy pośrodku nigdy nie widziały danych. Jeśli wartości potrzebuje wiele komponentów na różnych głębokościach, użyj kontekstu. Przy dużym stanie, który często się zmienia, rozważ bibliotekę stanu.

Czy kontekst to jedyne rozwiązanie prop drillingu?

Nie, i często nie najlepsze. Kompozycja z children usuwa drilling bez dodawania kontekstu i zostawia przepływ danych widoczny w kodzie.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ