Skocz do treści

Już wkrótce odpalamy zapisy na drugą edycję next13masters.pl. Zapisz się na listę oczekujących!

Komunikacja pomiędzy komponentami w React.js

Przyszedł ten moment, gdy Twoja aplikacja zaczyna się rozrastać i zmagasz się z różnymi problemami z architekturą. Zacznijmy od prostego problemu: Komunikacja pomiędzy komponentami. Ale nie tylko tymi, które są bezpośrednio obok siebie, ale też tymi dowolnie oddalonymi w aplikacji…

Ten artykuł jest częścią 18 z 43 w serii React.js.

Zdjęcie Michał Miszczyszyn
JavaScript10 komentarzy

Napisałem wcześniej podobne artykuły na temat AngularJS (link) i Angular2 (link). Wiedza tam zawarta jest w dużej części uniwersalna i możesz chcieć do nich zajrzeć.

Komunikacja w React.js

React to prosta biblioteka. Zasadniczo nie obchodzi jej w jaki sposób projektujesz architekturę swojej aplikacji. Ale zawarto w niej mechanizm na przekazywanie informacji z jednego komponentu do drugiego — z rodzica do dziecka — przy pomocy propsów. Tak jak robiliśmy to do tej pory. Ale przekazywać można nie tylko dane, ale też funkcje ;) W tym wpisie fragmenty kodu, a całość razem z testami znajdziesz na moim GitHubie: mmiszy/typeofweb-kurs-react/tree/part-4.

Rodzic ➜ Dziecko: Propsy

Rodzic przekazuje do swoich dzieci dane. Przykładowo: Aplikacja zawiera listę kontaktów, które muszą zostać przekazane do komponentu-dziecka, który je wyświetli. Znana sytuacja, prawda? ;)

React informuje nas, że propsy się zmieniły poprzez funkcję componentWillReceiveProps(nextProps) — dzięki czemu można zareagować na zmiany danego propsa. Ale raczej tego nie rób. Bo naprawdę rzadko jest to potrzebne. Troszkę więcej o tym w jednym z poprzednich wpisów:

https://typeofweb.com/metody-cyklu-zycia-komponentu-react-js/

Metody cyklu życia komponentu w React.js

Przy okazji omawiania komponentów będących klasami w React.js, wspomniałem też o możliwości korzystania z metod cyklu życia (lifecycle methods). Chciałbym do tego tematu teraz powrócić. Pokażę jakie metody cyklu życia definiuje React.js, do czego one służą i kiedy mogą się przydać.

W zasadzie to cała filozofia.

Dziecko ➜ Rodzic: Propsy (callback)

A teraz inna sytuacja. Coś się wydarzyło w komponencie-dziecku i musisz poinformować o tym rodzica. Przykładowo: Na naszej liście kontaktów, użytkownik zaznaczył kontakt, a rodzic musi wiedzieć, który kontakt został zaznaczony. Jak to zrobić?

React nie ma two-way data bindingu (jak AngularJS, Angular czy Vue). W tym przypadku to dobrze — bo komunikacja za pośrednictwem tego sposobu często prowadzi do bałaganu. Reactowy sposób jest inny: Rodzic może przekazać do dziecka funkcję. Następnie dziecko wywoła tę funkcję, gdy zechce poinformować rodzica o zmianach. To jest tak proste jak brzmi. Na przykładzie z listą kontaktów:

Dodaję nowe pole do state w App: selectedUser — będę tutaj przechowywał użytkownika, który został zaznaczony. Następnie wyświetlam go wewnątrz funkcji render. Pozostały kod pozostawiam bez zmian:

class App extends React.Component {
  constructor() {
    super();

    this.state = {
      filteredUsers: allUsers,
      selectedUser: null // tutaj
    };
  }

  render() {
    return (
      <div>
        {this.state.selectedUser}
        {/* … */}
      </div>
    );
  }
}

Teraz czas na przekazanie funkcji do dziecka. Tworzę więc nową funkcję w tym komponencie i przekazuję ją niżej:

onUserSelected = (selectedUser) => {
  this.setState({
    selectedUser
  });
}

render() {
  return (
    <div>
      {/* … */}
      <UsersList userSelected={this.onUserSelected} users={this.state.filteredUsers} />
    </div>
  );
}

Teraz modyfikuję UsersList i wywołuję w nim userSelected gdy użytkownik kliknie na kontakt:

<li onClick={userSelected.bind(null, user)} key={user}>{user}</li>

Dowolny komponent ➜ Inny Komponent

Tutaj magia Reacta się kończy ;) No, prawie, ale na temat context opowiem innym razem. Załóżmy na razie, że nie istnieje ;) Jak więc skomunikować ze sobą dwa komponenty, które leżą sobie gdziekolwiek w aplikacji? Na dowolny znany Ci sposób. To nie żart. Oto kilka wskazówek:

  • stwórz funkcję / klasę / obiekt — tzw. serwis, który posłuży Ci do komunikacji. Zaimportuj i użyj go w obu komponentach.
  • Przechowuj w nim dane lub wywołuj funkcje — podobnie jak w przypadku komunikacji rodzic ⟺ dziecko
  • Przyda Ci się znajomość wzorców projektowych, np. wzorca obserwatora. Więcej na temat samej koncepcji pod koniec mojego innego wpisu (link). Może ten gist (link) się nada?
  • Możesz użyć gotowych paczek, typu EventEmitter3 (link) lub podobnych.

Flux, Redux, MobX i co tam jeszcze…

Są też pewne ciekawe, rozbudowane i popularne rozwiązania: Architektura Flux i wywodzący się z niej słynny Redux, a także MobX. No i inne podobne biblioteki. Popełniłem już wcześniej jeden wpis na temat Fluksa i Reduksa od strony architektury. Jeśli hasła CQRS albo Event Sourcing nie są Ci obce to śmiało czytaj:

https://typeofweb.com/flux-i-redux-globalny-store-jednokierunkowy-przeplyw-danych/

Flux i Redux: globalny store i jednokierunkowy przepływ danych

Na co dzień korzystam z Redux razem z React. Dawniej używałem też własnej implementacji architektury Flux razem z AngularJS. Postanowiłem napisać o tym koncepcie coś więcej — bo jest to bez wątpienia bardzo ciekawe! Jednokierunkowy przepływ danych, akcje, dispatcher, action creator, reducer… to wszystko w tym wpisie :) A do…

Natomiast w kontekście Reacta — wrócę do tego, obiecuję ;) To temat pierwszy albo drugi wpis po tym! zapisz się na szkolenie z React.

Podsumowanie

Teraz już wiesz jak komponenty rozmawiają ze sobą. Wiedza na temat architektury aplikacji i wzorców projektowych przydaje się zawsze, niezależnie od frameworka, z którego korzystasz. Także tutaj. Ponownie — cały kod wraz z testami jest dostępny na moim GitHubie: https://github.com/mmiszy/typeofweb-kurs-react/tree/part-4

Jeśli chcesz na bieżąco dowiadywać się o kolejnych częściach kursu React.js to koniecznie śledź mnie na Facebooku i zapisz się na newsletter.

Ćwiczenie

Ćwiczenie:  Użyj biblioteki EventEmitter3 aby skomunikować ze sobą App i UsersList w powyższym przykładzie (bez przekazywania funkcji jako props). Czy udało Ci się bez problemu? Napisz w komentarzu!

👉  Znalazłeś/aś błąd?  👈Edytuj ten wpis na GitHubie!

Autor