Komunikacja pomiędzy komponentami w React.js

Ten wpis jest 18 częścią z 32 w kursie 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…

Napisałem wcześniej podobne artykuły na temat AngularJS (link)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:

Metody cyklu życia komponentu w React.js

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:

Flux i Redux

Natomiast w kontekście Reacta — wrócę do tego, obiecuję 😉 To temat pierwszy albo drugi wpis po tym!

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!