Přejít k navigační liště

Zdroják » JavaScript » React Hooks, které potřebujete znát

React Hooks, které potřebujete znát

Články JavaScript

React s verzí 16.8 přináší zásadní novinku, a tou jsou Hooks, které mají přinést řešení pro hned 3 největší problémy, se kterými se v Reactu potýkáme. Implementace komponent pomocí tříd, sdílení logiky, nepraktický způsob práce s životním cyklem komponent. Představíme vám React Hooks a na konci ukážeme funkční příklad.

Začneme hned prvním problémem, který se týká tříd v JavaScriptu a jejich použití pro definici komponent. V Reactu je možné vytvořit komponentu pomocí třídy anebo pomocí funkcí. Dlouho platilo to, že pokud chceme, aby komponenta měla vnitřní stav či životní cyklus, musíme zvolit třídu. V opačném případě ale můžeme použít funkci, která je syntakticky jednodušší a nemusíme zápasit s klíčovým slovem this, které činí vývojářům v JavaScriptu často problémy.

Hooks však představují primitiva, díky kterým může i funkcionální komponenta používat stav, životní cyklus a nejen to. Můžeme teď prohlásit, že díky Hooks pro nás může být funkcionální komponenta jasnou volbou, jelikož nemusíme řešit špatně odhalitelné bugy, které plynou ze zapomenutí bindování funkcí, případně použití arrow function, kvůli nelogickému chování klíčového slova this, které při použití s funkcemi odkazuje na kontext v místě volání funkce, ne její definice.

Sdílení logiky se, po zapovězení mixinů a příchodu tříd s ES2015, začalo řešit dvěma návrhovými vzory:

  • Higher Order Component a
  • Function as Child (nebo také Render Props).

Oba tyhle vzory mají své problémy. Function as Child je sice menší než HOC, ale zůstává Wrapper Hell (vaši komponentu máte zabalenou do velkého počtu obalujících funkcí kvůli sdílení logiky, což značně komplikuje strukturu komponent). React nyní umožňuje psát vlastní Hooks, které problém se sdílením logiky komponent řeší konečně správnou cestou.

Hooks umožňují i práci s životním cyklem. Jdou na to trochu jinak než metody obhospodařující životní cyklus v třídě. Běžně se v třídách stávalo, že logika, která spolu souvisí, je rozprostřená do několika metod životního cyklu, což přehlednosti zrovna nepomáhá. React nám nabízí například Hook useEffect, který dokáže pokrýt funkci hned třech metod životního cyklu, které spolu přímo souvisí, a tak je kód centralizovaný na jednom místě.

Hooks, které potřebujete pro každodenní práci

Pro práci se stavem máme useState

Díky useState můžeme, jak název napovídá, používat stav i v rámci funkcionálních komponent. Hook useState očekává parametr, který se stane iniciální hodnotou stavu, která je nastavená pouze poprvé, když je komponenta vytvořena. Pokud používáme TypeScript, tak můžeme tento Hook použít jako generickou funkci a předat jí typ našeho stavu. Hook nám vrátí dvojici, kterou získáme pomocí destrukturalizace. První je náš stav, v tomto případě číselná hodnota. Druhá je pak funkce, kterou můžeme zavolat a předat ji nějakou hodnotu, která se stane novým stavem, případně jí můžeme předat callback, který má jako parametr předchozí stav, na jehož základě můžeme vypočítat stav nový (podobně to bylo u metody setState, kterou známe z komponent vyrobených pomocí tříd).

import React from 'react'

function Component() {
  const [count, setCount] = React.useState(0);

  return ...;
}

Hook useState můžeme použít v komponentě kolikrát budeme chtít. Já osobně však používám jeden useState, který nenese primitivní hodnotu, ale objekt, do kterého můžu vložit, co potřebuji.

const [state, setState] = React.useState({ count: 0, ... })

Životní cyklus a useEffect

Tento Hook pokrývá hned tři momenty životního cyklu komponenty. Je to její zavedení do DOMu, její přerenderování, a případně její odebrání z DOMu. Praxe ukazuje, že často potřebujeme stejnou logiku provádět při zavedení komponenty do DOMu a při jejím přerenderování. Tento Hook se proto volá při obou těchto situacích.

Přes return může také vrátit callback, který funguje jako componentWillUnmount, a volá se při odebírání komponenty z DOMu.

useEffect(() => {
   // volá se při zavedení komponenty do DOMu a jejím přerenderování
   return () => {
      // volá se při odebírání komponenty z DOMu
   };
});

Určitě nastane situace, ve které nechceme, aby useEffect volal předaný callback pokaždé, ale pouze při změně nějakého parametru. Toho dosáhneme předáním pole parametrů, které pak useEffect kontroluje mezi přerenderováním komponenty a volá callback jen tehdy, pokud se hodnota změní.

useEffect(() => {
   // volá se při zavedení do DOMu a přerenderování, ale pouze pokud se změní props.value
}, [props.myValue]);

Nebo můžeme chtít použít useState čistě jen při zavedení komponenty do DOMu. Pak stačí toto pole předat prázdné. Pro přehlednost si můžete useEffect zabalit do vlastního Hooku, který příhodně pojmenujete useMount.

function useMount(effect) {
   useEffect(effect, [])
}

Dobré je si uvědomit, že useEffect probíhá až po vykreslení přerenderované komponenty v prohlížeči. Tedy ve chvíli, když uživatel vidí změny. Až poté se useEffect volá, aby zbytečně neblokoval fázi vykreslení. Většinou nám tento fakt nevadí, ale v případech, kdy chceme provést nějakou akci, která povede k dalšímu přerenderování komponenty a nechceme, aby uživatel viděl „probliknutí“ přerenderované komponenty, měli bychom použít obdobný Hook useLayout. Ten funguje úplně stejně, jen se volá ve stejnou chvíli, jako metody componentDidMount a componentDidUpdate v komponentách vytvořených pomocí třídy. Tyto metody se volají po zavedení změn po přerenderování komponenty do reálného DOMu, ale ještě před vykreslením těchto změn v prohlížeči.

Přístup k DOM elementům pomocí useRef

Občas potřebujeme nějaký ten rychlý a jednoduchý způsob přístupu k DOM elementu, a to nám právě umožňuje Hook useRef, který vrací objekt se členem „current“. Přes ten se dostaneme například k hodnotě inputu ve formuláři.

function TextInputWithButton() {
  const inputEl = React.useRef(null);

  const onButtonClick = () => {
    console.log(inputEl.current.value)
  };

  return (
    <div>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Get Input Value</button>
    </div>
  );
}

Navíc lze useRef použít k persistování nějaké hodnoty mezi přerenderováním komponenty. Můžeme například získat předchozí hodnotu props. Napíšeme si na to opět vlastní Hook.

function CounterDisplay(props) {
  const prevProps = usePrevious(props);
  return <h1>Now {props.count} {prevProps && prevProps.count}</h1>;
}

function usePrevious(value) {
  const ref = React.useRef();

  React.useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

…a řada dalších Hooks

React nabízí řadu vestavěných Hooks, které můžou být velmi užitečné. Například useContext pro snadné použití kontextu, useReduce pro sofistikovanou správu stavu na způsob Reduxu a další.

Jak si můžeme všimnou, Hooks jsou v zásadě jednoduché funkce, které nám navíc umožňují abstrahovat logiku mimo komponentu, a tak ji snadno sdílet a testovat.

Funkční ukázka

Připravil jsem pro vás jednoduchý todo list v CodeSandbox, na kterém předvádím použití základních Hooks rovnou s TypeScriptem, který je v posledních letech na vzestupu. Osobně ho doporučuji používat na všech typech projektů. Pokud uvažujete o přechodu na TypeScript, ale nechce se vám opouštět Babel (například kvůli rozmanitým možnostem konfigurace), doporučuji shlédnout krátký video tutoriál na youtube, kde ukazuji použití presetu pro Babel, který umožňuje snadnou integraci TypeScriptu.

Komentáře

Subscribe
Upozornit na
guest
33 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
L.

Já osobně však používám jeden useState, který nenese primitivní hodnotu, ale objekt,
do kterého můžu vložit, co potřebuji.

const [state, setState] = React.useState({ count: 0, … })

Což je ovšem antipattern. Nutí v vás psát nastavování stylem:

setState({...state, count: state.count + 1});

To je jednak zbytečně dlouhé, jednak ošklivé a hlavně to vede k chybám. Když statement viz výše provedete třeba v průběhu zpracování eventu 2x, tak se pak hrozně divíte, proč se čítač nezvýšil o 2 ale jen o 1, vždyť jste to přece spustili 2x… Podobně si můžete přepsat zpět změny v některých proměnných.

POKUD už byste chtěli mít víc proměnných v jednom objektu, které se mění (občas) zvlášť, tak na to je useReducer(). Ale jinak je mnohem lepší a čistší použít hezky pro každou nezávislou proměnnou její vlastní useState().

L.

Tak pardon, ještě upřesnění. Dohledal jsem, že i když o to React dokumentace nezmiňuje, i do set funkce useState hooku je možno dát funkci. Tedy pokud byste psal:

setState((s) => ({...s, count: state.s + 1}));

Tak by vícenásobné použití fungovalo OK. Nicméně stále je to další past, kód je komplikovanější a IMO je to zbytečné míchání dohromady věcí, které spolu nesouvisí.

L.

Tak pokud se vám může stát, že setCount() nenastavuje count, ale dělá něco úplně jiného, tak stejně se může stát, že setState() nenastavuje stav ale dělá něco úplně jiného, ne? To není ani tak otázka preferencí jako spíš kultury kódu.

Peter

Skratka hooky su nieco co nepotrebujete, lebo lepsie to ide bez nich pomocou class komponent :)

Peter

Nicméně směr Reactu je jasný.

Ano, smer Reaktu je jasny ako smer kazdej technologie ktoru ovladne peklo globalnych funkcii so side efektami. :)

Myslite ze OOP vzniklo preto aby sa pokazila krasa proceduralneho programovania, globalnych premennych a globalnych funkcii? :)

Peter

Btw OOP je celé o side efektech, nemyslíte?

Ano, samozrejme a OOP je o manazovani a enakpsulacii tych side efektov, preto tie classes.
To co robite vy a hooks je ak to nechapete iba ze ste nahradili class funkciou ktora uklada svoj stav na globalnej urovni v module. Chapete co tie hooky robia a preco je to svinstvo? Vy nadavate na class ale iba ste ho nahradili modulom? Chapete vy vobec co robite?

Peter

Uvedomujete si ze vase globalne premenne su moduly?
Takyto styl kodenia mozete robit iba pereto ze bezite v jedno vlakne.
Z hladiska OOP su vase moduly singletony a hoogks ich staticke metody.
Znovu sa pytam, chapete co robite?

Lee

Ano, JS běží v jednom vlákně. K čemu přesně je mi dobré, v kontextu Reactu, psát komponenty jako třídy?

Ten příklad prosím.

Vítek Heřman

Ahoj Pavle,

přestože se z toho kolega snaží asi dělat atomovou válku, tak mám pocit, že ve své podstatě naráží na konstrukci:

import React from 'react'

const [count, setCount] = React.useState(0)

count a setCount jsou zde proměnné modulu. Hned v úvodní dokumentaci v Reactu příklad vypadá trochu jinak:

import React from 'react'

function Component() {
  const [count, setCount] = React.useState(0);

  return ...;
}

V druhém případě jsou count a setCount v uzávěru funkce Component, nejsou tedy globální v modulu, ale lokální v komponentě (resp. uzávěru funkce)

Možná se mýlím, to jen takový rychlý postřeh :-)

Vítek Heřman

Není zač :-)

Trollům doporučuju se neomlouvat, nic jim nevysvětlovat, ani na ně nijak nereagovat. Jinak následuje akorát ještě blbější pocit. V článku chyba není, přišlo mi celkem zřejmé, že šlo o stručný ilustrační snippet. Jen jsem chtěl upozornit na absurditu toho, jaké záminky trollové používají pro své útoky a že za tím opravdu nějaké rozumné argumenty nebývají. Pokud je argument rozumný, lze ho obvykle i dobře zformulovat.

Lee

U nás s kolegy píšeme hooky od vydání Reactu 16.8. a žádné globální proměnné nepoužíváme. Očividně vůbec nevíte, o čem mluvíte. Nastudujte si téma. Taky by bylo pěkné, podložit co říkáte příkladem class components proti functional components s hooky, kde nám předvedete, o čem to celou dobu mluvíte. Když si ten článek (nebo třeba dokumentaci Reactu) přečtete, tak uvidíte, že stav (stav komponenty), případně jen lokální proměnnou, která si bude něco pamatovat, můžete řešit pomocí hooků useState/useReducer a useRef, kteté máte ve scope dané komponenty/custome hooku, takže žádné globální proměnné.

Ukažte příklad, kde prokážete, co tvrdíte.

L.

Doporučuji si přečíst článek. Tam máte jasně napsáno, v čem jsou výhody hooků. Jakmile byly ve stable, ihned jsme na ně přešli a nemůžeme si je vynachválit, jak nám kód zjednodušují a zpřehledňují.

Dám příklad: Řekněme, že mám v projektu několik komponent u kterých chci, aby mi po nějaké době od zobrazení vyhodili alert. Řešení s class komponentou (a service).

Service:

const TimeAlertSevice = {
  installAlert: (nazev) => window.setTimeout(
    () => alert(`Komponenta ${nazev] je zobrazena příliš dlouho`),
  5000);

  removeAlert: (handle) => window.clearTimeout(handle);
}

Vlastní třída:

 class MojeKomponenta extends React.Component {
   componentDidMount() {
     this.timeoutHandle = TimeAlertSevice .installAlert('MojeKomponenta ');
   }

   componentWillUnmount() {
     TimeAlertSevice.removeAlert(this.timeoutHandle);
   }
 }

Takovéhle použití sdílené funkcionality je možné, ale do používající komponenty se motá implementační logika – komponenta musí „vědět“, že musí zavolat jednu funkci při mount, schovat si číslo a s tím zavolat druhou funkci při unmount.

Je možné to napsat jako HOC, čímž se použití smrskne na aplikaci HOC a logika se tak nemotá do používajících komponent (a není vlastně třeba service), nicméně to zas vede k wrapper hell.

Za použití hooků je všechno velmi elegantní. Knihovní funkce:

  const useTimeAlert = (nazev) => useEffect(() => {
    const handle = window.setTimeout(
      () => alert(`Komponenta ${nazev] je zobrazena příliš dlouho`),
      5000
    );
    return () => window.clearTimeout(handle);
  }, []);

A použití:

  const MojeKomponenta = ({...props...}) => {
    useTimeAlert('MojeKomponenta');
    ... render
  }

Použití na jeden řádek, žádná implementační logika „neprosakuje“ ven, žádný wrapper hell, prostě jen čistý a čitelný kód :-)

Peter

Zbavíme se tříd (a řešení kolem this)

Nenechajte obmedzovat, navrhujem vam zajst s tou optimalizaciou a riesenim problemov este dalej: Zbavte sa aj funkcii. :)

Marfusios

No nevim, pro me osobne ukazka s tridou je krasne cista a citelna. Je proste videt exekucni flow a je mozne si na prvni dobrou domyslet kdy se ktera metoda zavola.

V druhem pripade bych byl bez dokumentace ztraceny.
Strasne moc nepochopitelnych return typu.
Nejake parametry vycucane z prstu ala [].
Ta zivotnost te useTimeAlert… bez dokumentace nejde odhadnout.

Zbyšek Lipka

muzu vedet co je to zde za syndrom ala Blesk?
ze po precteni nadpisu clanku znam obsah a podle toho se vyjadruji v diskuzi:
aneb – pokud byste si dal tu praci a claenk precetl tak rozhodne neni strasne moc nepochopitenych return typu ani parametru vycucanych z prstu :)

Peter

V clanku je asi dost klamstiev a polopravd, vid

https://stateofprogress.blog/the-biggest-lies-about-react-hooks-29aa306e354f

https://blog.logrocket.com/frustrations-with-react-hooks/

Odporucal by som autorovi clanku pisat podobne oslavne ody az po tom ako ziska praxou nalezite skusenosti a prehlad obmedzeni a probelmov danej technologie. :)

Editorovi zasa odporucam recenzovat clanky aby sa predislo sireniu klamstiev a polopravd.

Martin Hassman

Napište prosím, které konkrétní informace jsou špatně a proč. Pak se na to můžeme podívat.

Peter

Pozrite si prosim tie linky co som poslal, maju tam odvolavky. A samozrejme google. :)
Napriklad ze hooks su preto aby sa odstranili classes… blbost, to autor nieco nepochopil (okrem ineho).
Tento clanok a autor potvrdzuje to stare porekladlo ze ten kto to nevie to uci. :)

Martin Hassman

Já stále čekám, až napíšete, s čím konkrétně máte problém. Stačí citovat konkrétní věty z článku a napsat co je na nich špatně, jak by měly znít lépe. Rád se na to pak podívám. Zatím to jsou jen takové dojmy a pocity. S tím se nic nenadělá. Autor zatím (i zde v diskusi) odvádí dobrou práci.

shemale

neřešte ho Martine, je to slovák, který umí jen rejpat a nic neumí odůvodnit…

Peter

Opakovaně Vás žádám o ukázku kódu, kde mi předvede, jak hooks a
functional componenty škodí.

Citali ste moje komentare? ;)

Peter

Mimochodom tento clanok je na urovni hello world a vzhladom na profil autora ho mozno povazovat iba za PR clanok. Clanok je de facto osobna inzercia a promo autora.
Nic nove clanok neprinasa okrem tej averzie a fobie voci triedam a slovicku this.

Zbyšek Lipka

Dobry den,
bezne tady do diskuzi neprispivam, ale po precteni reakci diskutujiciho Peter jsem proste musel vyjadrit obdiv k vasi trpelivosti s lidmi postradajicimi zakladni programatorske kvality jako je treba logika ci vecna diskuze :)
p.s. a diky za clanek, zvladam bezne anglictinu, ale potesi si obcas precist neco o Reactu v materstine a taky je dobry vedet, ze v nasich lukach a hajich se nespi :)

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.

Pocta C64

Za prvopočátek své programátorské kariéry vděčím počítači Commodore 64. Tehdy jsem genialitu návrhu nemohl docenit. Dnes dokážu lehce nahlédnout pod pokličku. Chtěl bych se o to s vámi podělit a vzdát mu hold.