Logo

Koloběh DOMu

V předchozí kapitole jsme se bavili o tom, jak můžeme měnit stav aplikace. V této kapitole se podíváme na to, jak React manipuluje s DOMem (Document Object Model).

Ještě než začneme, musíme si vysvětlit několik pojmů:

  • DOM – Document Object Model – DOM je objekt, který reprezentuje HTML dokument. Jedná se o stromovou strukturu, ve které jsou uloženy všechny elementy HTML dokumentu.
  • VDOM – Virtuální DOM – Vykreslování do DOMu je pomalé. Proto si React vytvořil VDOM, kde provádí všechny výpočty a přípravy na vykreslení. Do skutečného DOMu pak předává jen výsledné změny.
  • Diff/Diffing – Proces hledání rozdílů mezi starým a novým stavem.

React DOM lifecycle

Ve virtuální fázi provádí React výpočty na svém VDOMu. Pomocí tzv. diffingu zjistí, co je třeba změnit a jak to provést co nejmenším počtem operací. V této fázi se zatím nic nevykresluje.

V této fázi začne React propisovat změny do reálného DOMu. Změny se tedy stávají viditelnými.

V této fázi začíná React zpracovávat události, například eventy z funkcí useState nebo useEffect. Pokud dojde ke změnám, React se vrací zpět do virtuální fáze, přepočítá změny a znovu je vykreslí.


Možná jste při práci s Reactem narazili na tuto chybovou hlášku:

Each child in a list should have a unique "key" prop.

Check the render method of `List`. See https://react.dev/link/warning-keys for more information.

Příklad kódu:

const uzivatele = [{ jmeno: 'Jan', id: 1 }, { jmeno: 'Petr', id: 2 }];
<ul>
  {uzivatele.map(clovek => (
    <li>{clovek.jmeno}</li>
  ))}
</ul>

Tato hláška znamená, že React neví, jak jednotlivé prvky v seznamu správně seřadit a identifikovat.

Podívejme se, jak tento kód bude vypadat v prohlížeči:

<ul>
  <li>Jan</li>
  <li>Petr</li>
</ul>

Co se ale stane, když na začátek listu přidáme nového uživatele Pepa?
React bude muset vykreslit celý seznam znovu, což může výrazně zpomalit aplikaci.
Pokud ale každému elementu přiřadíme unikátní klíč (key), React si uvědomí, že se prvky pouze přesunuly, a vykreslí jen rozdíly:

<ul>
  {uzivatele.map(clovek => (
    <li key={clovek.id}>{clovek.jmeno}</li>
  ))}
</ul>

Výsledkem bude mnohem efektivnější renderování.


Co se stane, když kliknete na toto tlačítko – o kolik se zvýší hodnota? O 1, 2, nebo 4?

import { useState } from 'react';
export default function App() {
const [count, setCount] = useState(0);
const upCount = () => {
  setCount(count + 1);
  setCount(count + 1);
  setCount(count + 2);
};
return (
  <div>
    <h1>Počet kliknutí: {count}</h1>
    <button onClick={() => upCount()}>X</button>
  </div>
);
}

Výsledek? Hodnota se zvýší pouze o 2.

Proč?
React před každým renderem vytvoří snímek stavu (snapshot) a všechny změny zpracuje nad tímto původním stavem. To znamená, že předchozí dvě volání setCount(count + 1); vlastně používají stále stejný výchozí stav, takže se navzájem přepisují.

Pokud chceme, aby setCount() bral v potaz skutečný předchozí stav, měli bychom použít tzv. funkcionální zápis:

setCount((predchoziStav) => predchoziStav + 1);

Tím zajistíme, že každá změna bude založena na tom nejnovějším dostupném stavu.