• Post last modified:06/04/2020
  • Reading time:7 mins read
  • Post comments:0 Komentarzy

Jednym z bardzo ciekawych narzędzi wbudowanych w React w wersji 16. są tzw. Portale (React Portals). Sama zasada działania Portali jest bardzo prosta, warto więc poznać nieco bliżej ten mechanizm.

Info

Post ten został napisany w czasie gdy w Reakcie nie istniały jeszcze hooki, więc przykłady oparte są na komponentach klasowych, jednak logika i sposób postępowania w obydwu przypadkach jest taki sam. Należy również pamiętać, iż klasy nadal są jak najbardziej poprawnym sposobem tworzenia komponentów.

Portal

Czym tak w ogóle jest React Portal i do czego może nam się przydać? Dzięki portalom możemy swobodnie decydować w którym miejscu chcemy wyrenderować dowolny komponent w drzewie DOM. Dowolnym, czyli niezależnie od tego w którym miejscu naszej aplikacji jest on tworzony. Przykładowo, możemy mieć bardzo rozbudowaną aplikację w której zagnieździliśmy pewien komponent dość głęboko wewnątrz innych komponentów. W związku z tym prawdopodobnie zostanie on również zagnieżdżony dość głęboko w drzewie DOM. Gdyby ten komponent był modalem bądź powiadomieniem typu toast prawdopodobnie chcielibyśmy, aby wyświetlił on się nad wszystkimi innymi elementami w przypadku modalu, bądź w którymś z narożników ekranu w przypadku toasta bądź innego powiadomienia.

Stylowanie tego komponentu z użyciem CSS oraz zważanie na wszystkie elementy po drodze posiadające atrybuty takie jak overflowz-index czy absolutne pozycjonowanie może być dość trudnym zadaniem. Tutaj z pomocą przychodzi nam React Portal.

Użycie

Renderując komponent klasycznie, wszystko co zostanie zwrócone przez metodę render() zostaje dołączone w DOM jako children komponentu wywołującego.

Taka oto przykładowa struktura komponentów:

<Wrapper>
  <MyComponent>
    <FormComponent>
      <Content />
      <Buttons />
      <InfoBox />
    </FormComponent>
  </MyComponent>
</Wrapper>

wyrenderuje nam w DOM coś co może wyglądać następująco (uproszczona struktura):

<body>
  <div>
    <div>
      <h1>ComponentTitle</h1>
      <p>Component description</p>
      <div>
        <h2>Form Title</h2>
        <form>
          <label>Question1</label>
          <input type=text />
          <label>Question2</label>
          <input type=text />
          <input type=submit value="Send"/>
          <input type=button value="Info"/>
        </form>
        <div id="info-box" hidden>
          <p>I am here to help you fill the form </p>
        </div>
      </div>
    </div>
  </div>
</body>

W powyższym przypadku InfoBox może być modalem z instrukcją wypełniania formularza. Wyrenderuje on się dość głęboko w DOM i naszym zadaniem będzie tak wystylizować go za pomocą CSS, aby wyświetlił się nad wszystkimi innymi elementami. Dużo łatwiej byłoby to zrobić w przypadku gdyby modal był wyrenderowany w DOM jako pierwszy child tagu body i jednocześnie nie zmienił swojego położenia w drzewie komponentów Reacta, np. aby mógł wspóldzielić stan oraz otrzymywać propsy od komponentu-rodzica. Tak, jest to możliwe przy użyciu Portali.

W przypadku użycia Portalu, w metodzie render() nie zwracamy komponentu reactowego, natomiast zwracamy ReactDOM.createPortal(komponent-do-wyrenderowania, miejsce-w-DOM):

const modals = document.getElementById('modals');

class Modal extends React.Component {
  render() {
    return ReactDOM.createPortal(
      <div>Content</div>, // or eventually this.props.children,
      modals,
    );
  }
}

Jak widać, składnia metody createPortal() jest dosyć prosta – jako pierwszy argument podajemy komponent do zamontowania w DOM (bez Portalu to byłby ten komponent normalnie zwracany w metodzie render), natomiast drugim elementem jest dowolne istniejące miejsce w całym drzewie DOM.

Kiedy używać?

React Portal najczęściej sprawdzi się właśnie w przypadku modali, tooltipów, pływających elementów na stronie, widżetów, powiadomień. Jego możlwiości pozwalają również na wiele więcej. Przykładem może być współdzielenie stanu komponetu pomiędzy elementami renderowanymi w dwóch różnych oknach przeglądarki. Zrobimy to dzisiaj w dalszej części artykułu.

Scope

Świetną sprawą w przypadku Portali jest to, iż komponent wyrenderowany w Portalu zachowuje się tak, jakby nadal znajdował się w drzewie Reacta w tym miejscu, gdzie została wywołana metoda render() – tak więc możemy przekazaywać mu propsy oraz implementować cykle życia komponentu. W zaprezentowanym za chwilę przykładzie nr 1 będzie to dużo lepiej widoczne.

Przykład 1 – modal oraz powiadomienie

Info

Zarówno ten jak i następny przykład polecam przeglądać na urządzeniach innych niż mobilne, gdyż mogą wystąpić problemy z prawidłowym wyświetlaniem ramki pochodzącej z CodeSandbox.

W pierwszym przykładzie będziemy implementować wywołanie modalu oraz powiadomienia sukcesu:

Spójrzmy najpierw na plik /public/index.html. Widzimy tam w tagu body trzy div-y:

  • „root”, w którym znajduje się główna aplikacja,
  • „modals”, w którym renderujemy modale,
  • „toasts”, w którym renderujemy wszelkiego rodzaju powiadomienia.

Teraz skupmy się na pliku src/index.js – znajduje się tam cała logika aplikacji. Posiadamy w niej trzy komponenty:

  • App: główny komponent (kontener) zawierający w sobie pozostałe dwa komponenty,
  • Modal: komponent służący do renderowania modali,
  • Toast: komponent do renderowania powiadomień.

Wyświetlenie bądź ukrycie komponentów Modal oraz Toast zależy od stanu komponentu App. O ile metoda render() w komponencie App zwraca nam to co ma być wyrenderowane w DOM w sposób „klasyczny”, tak Modal i Toast zostają wyrenderowane w Portalach. Te z kolei montują je w div-ach o których wspominałem na samym początku – modals i toasts.

Dzieki temu stylizowanie tych komponentów (plik /src/styles.css) jest już dużo łatwiejszym zadaniem.

Klikamy przycisk info i spójrzmy na DOM:

rozwinięte drzewko DOM

Jak widać, modal nie wyrenderował się wewnątrz głównej aplikacji, tylko wewnątrz elementu o id=”modals” a mimo wszystko jest sterowany stanem komponentu App.

Dla porównania sprawdźmy jak wygląda drzewo komponentów React:

Rozwinięte drzewko DOM react

Tak jak już wcześniej wspomniałem – z punktu widzenia Reacta, komponent Modal nadal jest zagnieżdżony w komponencie App. Dokładnie taką samą sytuację będziemy mieli w przypadku komponentu Toast – zostawiam to już do samodzielnego przetestowania.

Przykład 2 – nowe okno

W drugim przykładzie spróbujemy zaimplementować React Portals w taki sposób, aby nasza aplikacja otwierała nowe okno przeglądarki, wewnątrz którego wyrenderujemy kolejny komponent. Komponent ten będzie współdzielił stan komponentu wywołującego.

Poniżej znajduje się przykładowa implementacja. Ponownie cały kod aplikacji znajduje się w pliku src/index.js. Komponent App jest głównym komponentem, natomiast w nowym oknie przeglądarki będziemy próbowali renderować komponent NewWindow.

Umieściłem w kodzie komentarze które powinny pomóc w bezproblemowym zrozumieniu tej implementacji. Zasada działania Portalu jest dokładnie taka sama jak w przykładzie pierwszym.

Podsumowanie

Jak widać na powyższych przykładach React Portals jest bardzo użyteczną funkcjonalnością wbudowaną w Reacta. Pozwala nam ona zaoszczędzić sporo czasu i linii kodu potrzebnych do implementacji niestandardowego renderowania komponentów. Myślę, że zdecydowanie warto jest go stosować w naszych aplikacjach już dzisiaj.

Kamil Józwik

Frontend developer👨‍💻 Autor kursów na frontschool.pl, małego narzędzia dla programistów - frontbook.dev oraz kolejnych postów na tym blogu. Ulubiony stos technologiczny to React wraz z TypeScript oraz wszystko, co "nowe" w tym pięknym dynamicznym front end-owym świecie 🙂
Subscribe
Powiadom o
guest
0 Comments
Inline Feedbacks
View all comments