Komunikacja pomiędzy komponentami w React.js

Ten wpis jest 18 częścią z 41 w kursie React.js
wp-content/uploads/2017/10/React_logo_wordmark-300x101-1-e1508612391308.png
  1. React.js: Wprowadzenie do kursu od podstaw
  2. Poznaj React.js
  3. Pierwszy komponent w React.js
  4. Props czyli atrybuty w React.js
  5. Podział na komponenty w React.js
  6. Klasy jako komponenty React.js
  7. Interakcja z komponentami React.js
  8. Stan komponentów React.js
  9. State w React.js 2
  10. Metody cyklu życia komponentu w React.js
  11. React.js w przykładach: filtrowanie statycznej listy
  12. Tworzenie aplikacji React.js dzięki create-react-app
  13. React.js na GitHub Pages dzięki create-react-app
  14. Testowanie aplikacji React.js — podstawy Enzyme
  15. Testowanie React.js w Enzyme — props, state i interakcje
  16. Poprawne bindowanie funkcji w React.js
  17. Odpowiadam na pytania: Babel, ECMAScript, destrukturyzacja, onClick, className
  18. Komunikacja pomiędzy komponentami w React.js
  19. Komunikacja z API w React.js
  20. Formularze w React.js — kontrolowane komponenty
  21. Formularze w React.js — niekontrolowane komponenty
  22. Odpowiadam na pytania: props, nawiasy klamrowe, funkcje vs klasy, import react
  23. TDD w React.js z pomocą react-testing-library
  24. Flux i Redux: globalny store i jednokierunkowy przepływ danych
  25. React + Redux — kurs: wprowadzenie i podstawy
  26. React + Redux — filtrowanie listy, proste selektory
  27. Projektowanie komponentów: Presentational & Container Components
  28. Asynchroniczność w Redux: redux-thunk
  29. Kiedy używać state, a kiedy Redux?
  30. Nowe metody cyklu życia: getDerivedStateFromProps i getSnapshotBeforeUpdate
  31. Leniwe ładowanie komponentów w React dzięki import
  32. Higher Order Reducers — Redux i powtarzanie kodu
  33. React Hooks — wprowadzenie i motywacja
  34. React Hooks: useState, czyli stan w komponentach funkcyjnych
  35. React Hooks: useState — wiele stanów, callbacki i inne niuanse
  36. React Hooks: useEffect — efekty uboczne w komponencie
  37. React Hooks a żądania do API
  38. useReducer — przenoszenie logiki poza komponent
  39. useMemo, useCallback, czyli rozwiązanie problemów ze zmieniającymi się propsami
  40. Wady React Hooks
  41. React Hooks: Piszemy własne hooki!

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! Naucz się React na szkoleniu z Type of Web!

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.

Nie wysyłamy spamu, tylko wartościowe informacje. W każdej chwili możesz się wypisać klikając „wypisz się” w stopce maila.

Ć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!

Nawigacja po kursie:
  1. React.js: Wprowadzenie do kursu od podstaw
  2. Poznaj React.js
  3. Pierwszy komponent w React.js
  4. Props czyli atrybuty w React.js
  5. Podział na komponenty w React.js
  6. Klasy jako komponenty React.js
  7. Interakcja z komponentami React.js
  8. Stan komponentów React.js
  9. State w React.js 2
  10. Metody cyklu życia komponentu w React.js
  11. React.js w przykładach: filtrowanie statycznej listy
  12. Tworzenie aplikacji React.js dzięki create-react-app
  13. React.js na GitHub Pages dzięki create-react-app
  14. Testowanie aplikacji React.js — podstawy Enzyme
  15. Testowanie React.js w Enzyme — props, state i interakcje
  16. Poprawne bindowanie funkcji w React.js
  17. Odpowiadam na pytania: Babel, ECMAScript, destrukturyzacja, onClick, className
  18. Komunikacja pomiędzy komponentami w React.js
  19. Komunikacja z API w React.js
  20. Formularze w React.js — kontrolowane komponenty
  21. Formularze w React.js — niekontrolowane komponenty
  22. Odpowiadam na pytania: props, nawiasy klamrowe, funkcje vs klasy, import react
  23. TDD w React.js z pomocą react-testing-library
  24. Flux i Redux: globalny store i jednokierunkowy przepływ danych
  25. React + Redux — kurs: wprowadzenie i podstawy
  26. React + Redux — filtrowanie listy, proste selektory
  27. Projektowanie komponentów: Presentational & Container Components
  28. Asynchroniczność w Redux: redux-thunk
  29. Kiedy używać state, a kiedy Redux?
  30. Nowe metody cyklu życia: getDerivedStateFromProps i getSnapshotBeforeUpdate
  31. Leniwe ładowanie komponentów w React dzięki import
  32. Higher Order Reducers — Redux i powtarzanie kodu
  33. React Hooks — wprowadzenie i motywacja
  34. React Hooks: useState, czyli stan w komponentach funkcyjnych
  35. React Hooks: useState — wiele stanów, callbacki i inne niuanse
  36. React Hooks: useEffect — efekty uboczne w komponencie
  37. React Hooks a żądania do API
  38. useReducer — przenoszenie logiki poza komponent
  39. useMemo, useCallback, czyli rozwiązanie problemów ze zmieniającymi się propsami
  40. Wady React Hooks
  41. React Hooks: Piszemy własne hooki!

Nie wysyłamy spamu, tylko wartościowe informacje. W każdej chwili możesz się wypisać klikając „wypisz się” w stopce maila.

Subscribe
Powiadom o
guest
11 komentarzy
Most Voted
Newest Oldest
Inline Feedbacks
View all comments
Konrad Cieślukowski
Konrad Cieślukowski
2 lat temu

co do wpuszczania parametru do event handlera przekazywanego z rodzica, to nie lepiej tak:
onClick={() => userSelected(user)} ?

Piotr Aueternum
2 lat temu

Będzie coś o render props?

trackback

[…] potrzebny Redux 😉 Jeśli nie pamiętasz to odśwież sobie pamięć na temat komunikacji komponentów w React. Dlatego pozwolę sobie wydzielić zupełnie nowy komponent, który za zadanie będzie […]

Adam Pi
Adam Pi
1 rok temu

Ja mam problem z emitter. Tutaj mój kod: https://codesandbox.io/s/l984n0m1w7
Podpinam wszystko jak w tutorialach i nadal nie uruchamia się nawet prosty console.log().
Co zrobiłem źle/czego nie zrobiłem?

Navgar
Navgar
1 rok temu

Przeszedłem przez cały kurs do tej pory, ale ćwiczenie jest zbyt mało zrozumiałe dla mnie.
Jak się używa biblioteki EventEmitter3? Co konkretnie mam komunikować?

Byłoby dobrze, gdybyś mógł napisać to w formie:
Skomunikuj {ten komponent } z {tym komponentem}, aby po przekazaniu {czegoś tam} do {tego komponentu}, było widoczne {to}, w {tym komponencie}.

Dziękuję za kurs, super sprawa!

Kasianie
Kasianie
1 rok temu

Cześć 🙂 Udało się, ale nie jest pewna czy aby nie przekombinowałam. Nie wiedziałam jak przekazać usera do onClick w map. Jak lepiej to zrobić?
EventEmitter w osobnym pliku EmitterBase.

import React from "react";
import ReactDOM from "react-dom";
import EmitterBase from "./EmitterBase";

const userEmitter = new EmitterBase();
const allUsers = ["Asia", "Basia", "Witek", "Stefan"];

class UserList extends React.Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
}
onClick(event) {
let selectedUser = event.target.innerText;
userEmitter.emit("CLICKED_ITEM", { selectedUser });
}
render() {
if (this.props.users.length > 0) {
return (

{this.props.users.map(user => (

{user}

))}

);
}
return No results!;
}
}

class App extends React.Component {
constructor() {
super();
this.state = {
filteredUsers: allUsers,
selectedUser: null
};
}
filterUsers = e => {
const text = e.currentTarget.value;
const filteredUsers = this.getFilteredUsersForText(text);
this.setState({
filteredUsers
});
};
getFilteredUsersForText = text => {
return allUsers.filter(user =>
user.toLowerCase().includes(text.toLowerCase())
);
};
onUserSelected = ({ selectedUser }) => {
this.setState({
selectedUser: selectedUser
});
};

componentDidMount() {
userEmitter.on("CLICKED_ITEM", this.onUserSelected);
}

componentWillUnmount() {
userEmitter.removeEventListener("CLICKED_ITEM", this.onUserSelected);
}

render() {
return (

{this.state.selectedUser}

);
}
}

ReactDOM.render(, document.getElementById("root"));