7

Komunikace mezi sourozenci

Nejnáročnejší typ komunikace mezi komponentami je předávání informací mezi sourozenci.

Jakmile máme v naší React aplikaci více než jednu komponentu, vždycku musíme řešit jejich vzájemnou komunikaci. V minulé lekci jsme si ukázali, jak posílat informace mezi rodičem a dítětem. Často nám však praxe přichystá ještě složitější situaci, kdy potřebujeme zařídit komunikaci mezi sourozenci nebo dokonce mezi vzdálenými potomky. Tyto situaci si rozebereme v dnesní lekci.

Destrukturování props

V profesionálně napsných React komponentách se často setkámi s takzvaným destrukturováním props. Než se k němu dostaneme, pojďme si v krátkosti připomenout destrukturování jako takové.

Mějme například takovýto objekt představující nějakou vlakovou nebo autobusovou jízdenku.

const ticket = {
  price: {
    amount: 12,
    currency: 'EUR',
  },
  from: 'Prague',
  destination: 'Vienna',
  stops: ['Kolín', 'Pardubice', 'Brno', 'Břeclav'],
};

Pokud chceme z tohoto objektu získat startovní a cílovou destinací, můžeme si je uložit do proměnných například takto.

const from = ticket.from;
const destination = ticket.destination;

Ke zkrácení tohoto zápisu však můžeme s výhodou použít destrukturování.

const { from, destination } = ticket;

Kdybychom v naší webové aplikaci chtěli jízdenku nějak zobrazit, mohli bychom mít komponentu Ticket napsanou například takto:

const Ticket = (props) => {
  return (
    <div className="ticket">
      <div className="ticket__from">{props.from}</div>
      <div className="ticket__to">{props.destination}</div>
    </div>
  );
};

Vzhledem k tomu, že v parametre props je vždy objekt, můžeme jej na začátku komponenty destrukturovat a ušetřit si pak trochu psaní v JSX.

const Ticket = (props) => {
  const { from, destination } = props;
  return (
    <div className="ticket">
      <div className="ticket__from">{from}</div>
      <div className="ticket__to">{destination}</div>
    </div>
  );
};

Tento zápis má výhodu v tom, že hned na prvním řádku komponenty vidíme, jaké props komponenta očekává, a nemusíme tak kvůli tomu číst celý její kód. Nešikovné však je, že se nám tímto kód komponenty prodlužuje. Pokud chcete být opravdoví profíci, uděláte destrukturování přimo v parametru funkce.

const Ticket = ({ from, destination }) => {
  return (
    <div className="ticket">
      <div className="ticket__from">{from}</div>
      <div className="ticket__to">{destination}</div>
    </div>
  );
};

Diky tomuto triku jsme zároveň zařídili, že komponenta dělá pouze return. Můžeme tedy nakonec použít zkracování.

const Ticket = ({ from, destination }) => (
  <div className="ticket">
    <div className="ticket__from">{from}</div>
    <div className="ticket__to">{destination}</div>
  </div>
);

Protože samozřejmě všichni chceme být opravdoví profící, budeme od této chvíle naše komponenty psát vždy tímto způsobem.

Komunikace mezi sourozenci

Komunikaci mezi sourozenci si opět ilustrujeme na našem příkladu s volbou nového prezidenta. V minulé lekci jsme skončili v situaci, kdy komponenta Candidate dokáže svému rodiči App poslat jméno kandidáta pomocí callbacku. Naše aplikace však byla napsaná velmi jednoduše. V praxi bychom nejspíše narazili na složitější situaci. Podívejme se na začátek komponenty App.

return (
  <div className="container">
    <div className="castle">
      <div className="castle__image"></div>
      <div className="castle__body">
        <h1>Nový prezident</h1>
        <p className="castle__president">
          {
            president === null ? 'Vyberte svého kandidáta' : president
          }
        </p>
      </div>
    </div>

Tento úsek představující náš hrad by si jistě zasloužil samostatnou komponentu. Pojmenujme ji Castle a vytvoříme pro ni separátní složku i styly. Kód komponenty pak bude vypadat takto.

import React from 'react';
import './style.css';

const Castle = ({ president }) => {
  return (
    <div className="castle">
      <div className="castle__image"></div>
      <div className="castle__body">
        <h1>Nový prezident</h1>
        <p className="castle__president">
          {
            president === null ? 'Vyberte svého kandidáta' : president
          }
        </p>
      </div>
    </div>
  );
};

export default Castle;

Naše komponenta App pak může komponentu Castle použíjt jako svoje dítě.

return (
  <div className="container">
    <Castle president={president} />
    
    <h2>Kandidátí</h2>
);

Všimněte si, že komponenta Candidate a komponenta Castle jsou sourozenci. Jejich společným rodičem je komponenta App. Komponenta Candidate už neposílá jméno kandidáta svému rodiči jako dříve. Nyní jej posílá svému svému sourozenci Castle. Všimněte si však, že tato komunikace probíhá skrze rodiče App. Jakmile komponenta Candidate zavolá callback handleVote, tento uloží jméno prezidenta do stavu komponenty App. Tímto se vyvolá překreslení komponenty App, čimž se hodnota stavu president předá do props komponenty Castle.

Tento princip možná na první pohled vypadá složitě. Výhodou však je, že zůstává vždy stejný. Vždy, když chceme předávat informace mezi sourozenci, předáváme je skrze stav rodiče.

Cvičení: Komunikace mezi sourozenci

1

Dotazník

to dáš

V tomto cvičení budeme vytvářet jednoduchou aplikaci na tvorbu dotazníků. Základ aplikace najdete v tomto repozitáři.

  1. Udělejte si fork repozitáře se zadáním a naklonujte si repozitář do počítače.
  2. Otevřete složku dotaznik ve VS Code a v terminálu spousťte instalaci závíslostí pomocí npm install. Poté aplikaci spusťte.
  3. Prohlédněte si vzhled aplikace a také její zdrojový kód. Nejsme už žádní troškaři, aplikace se skládá hned z pěti oddělených komponent. Prohlédněte si, jak jsou navzájem propojené a seznamte se s celkovou strukturou aplikace. Komponentu Icon zatím nezkoumejte. Je v ní trochu nepřehledný kód pro zobrazování ikonek.
  4. Prohlédněte si komponentu Option. Přepište ji tak, aby používala destrukturování props.
  5. Totéž proveďte s komponentami Question a QuestionBody.
2

Dotazník s fajfkou

zapni hlavu

Pokračujme v příkladu z předchozího cvičení. Budeme chtít zařídit, že po kliknutí na jednu z 5 možných odpovědí se u otázky zobrazí ikonka fajfky.

  1. Nejprve chceme u otázky reagovat na kliknutí na jednu z jejich možností. V komponentě Option přidejte divu s třidou option posluchač události onClick. Zatím jej naprogramujte tak, aby do konzole vypsal text vybrané odpovědi, tedy například “spíše souhlasím”.
  2. V komponentě Question si vytvořte stav answered. Tento stavu bude říkat, zda je otázka zodpovězena, či nikoliv. Výchozí hodnota tohoto stavu bude false.
  3. Zařiďte následující: pokud je ve stavu answered hodnota false, chceme, aby componenta QuestionBody měla ikonku symbolQuestion. Pokud je stav true, chceme, aby typ ikonky byl symbolTick.
  4. Nyní chceme komponentě Option přidat callback, pomocí kterého může informovat svého rodiče, že otázka byla zodpovězena. Přidejte tedy komponentě Option prop s názvem onSelected. V této prop očekáváme funkci. Zařiďte, aby komponenta Option zavolala funkci onSelected ve chvíli, kdy na tuto komponentu klikneme.
  5. V komponentě Question si vytvořte funkci handleSelect. Všem komponentám Option pak skrze prop onSelected tuto funkci předejte. Funkce handleSelect nechť nastaví stav answered na hodnotu true. Takto zajístíme, že kdykoliv uživatel klikne na nějakou možnost, stav se nám nastaví na true a tím se změní ikonka v těle otázky na fajfku.

Takto jsme zařítili, že dvě děti Option a QuestionBody spolu komunikují skrze stav rodiče.

3

Dotazník s odpovědí

zapni hlavu

Pokračujme v příkladu z předchozího cvičení. Nyní budeme chtít místo fajfky zobrazovat přímo uživatelem vybranou odpověd.

  1. Stav answered uvnitř komponenty Question přejmenujte na answer. Nýní bude obsahovat řetězec s typem ikonky pro QuestionBody. Výchozí hodnota stavu bude symbolQuestion.
  2. Zařiďte, aby komponenta QuestionBody zobrazovala místo symbolQuestion nebo iconTick ikonku odpovědi, na kterou uživatel kliknul.
  3. Nyní budeme potřebovat, aby nám komponenta Option skrze callback předala typ ikonky, na kterou uživatel kliknul. Komponenta Option tedy musí callbacku onSelected předat typ ikony, kterou zobrazuje.
  4. Funkce handleSelect v komponentě Question bude pak mít jeden parametr, který nazveme iconType. Jakmile se tato funkce zavolá, nastavíme náš stav answer na hodnotu získanou v tomto parametru.

Tímto jsem zařídili, že komponenta Option předá svému sourozenci QuestionBody informaci o tom, kterou ikonku uživatel vybral.