React Hooks, které potřebujete znát

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.
Nálepky:
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.
Což je ovšem antipattern. Nutí v vás psát nastavování stylem:
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().
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:
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í.
Dobrý den. Je to jak sám píšete a jak píšu i já v odstavci o useState, kde právě předání funkce popisuji. Upřednostňuji používání useState tímto způsob, jelikož to je při práci se stavem přímo patrné. Když budu mít místo volání setState(…) někde v kódu setCount(…), tak na první pohled netuším, že tím nastavuji stav, protože to může být funkce, která dělá něco úplně jiného. Je to, hádám, otázka preferencí. Oba přístupy jsou možné a mají své opodstatnění.
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.
setState je obecně známý název funkce pro nastavení lokálního stavu, která je známá už od class komponent, takže každému její název evokuje, co dělá, ale setCount může být nějaký callback, který jsem například dostal v props a nemusí s lokálním stavem komponenty souviset.
Skratka hooky su nieco co nepotrebujete, lebo lepsie to ide bez nich pomocou class komponent :)
Takto můžete říct, že nepotřebujete ani React, protože si vystačíte s HTML a vanilla JavaScriptem.
Jak popisuje článek, hooks řeší hned 3 problémy class komponent.
Sdílení kódu mezi class komponentami pomocí HoC/Render Props je oproti hooks prostě pain, který vede k wrapper hell. Komponenty jsou navíc díky hooks menší.
Nic Vám nebrání stále používat class komponenty. Hooks nepřináší žádné breaking changes. Nicméně směr Reactu je jasný. Je možné (zde pouze můj názor), že jednou budou class komponenty prohlášeny za obsolete.
Pro inspiraci doporučuji například tento záznam z React Conf.
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? :)
V JS máme moduly, do kterých hooks (stejně jako jiné funkce/třídy/cokoliv) můžete organizovat a zároveň vás hooks rozhodně nenutí používat jakékoliv globální proměnné, pokud to tak nevyžaduje use case. Tedy žádné globální funkce měnící globální proměnné nevznikají, ale pokud potřebujete například manipulovat s document.title (jako já v příkladu), děláte to v hooku úplně stejně, jako by se to udělalo v class komponentě.
Btw OOP je celé o side efektech, nemyslíte?
Když budu chtít sdílet logiku mezi class komponentami, mohu použít například HoC, což bude znamenat vytvoření class komponenty, která danou logiku ponese a bude ji poskytovat komponentám, které wrappuje. Pokud budu chtít to samé řešit přes hooks, tak budu muset vytvořit custom hooks, což je funkce, která si uvnitř bude řešit vše, co class komponenta, pouze je kratší, čitelnější a zbavím se wrapper hell.
Můžete prosím uvést příklad, na kterém bude zjevný benefit class komponent oproti hooks?
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?
Jiný komentující Vám sem vkládal ukázku, která jasně předvádí použití hooks vs. class komponenty. Znovu Vás žádám, ukažte vlastní příklad, který předvede, že hooks nutí psát globální proměnné, když do nich přepíšete ekvivalentní funkcionalitu z class komponenty.
Představa, že potřebujete s hooks používat globální proměnné, je naprosto lichá.
Doporučuji nejprve projít dokumentaci a zjistit, co hooks vlastně jsou, než budete pokračovat v diskuzi.
Ukažte příklad, kdy budete demonstrovat, co tvrdíte, jinak se nepohneme z místa.
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?
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.
Bavíme se ještě o Reactu? Ten příklad prosím. Na něm nejlépe ukážete, v čem v jednovláknovém JS bude dobré psát komponenty jako třídy, a také jak řešíte například sdílení logiky mezi class komponentami. Děkuji.
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:
count a setCount jsou zde proměnné modulu. Hned v úvodní dokumentaci v Reactu příklad vypadá trochu jinak:
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 :-)
Ahoj Vítku :-). Máš pravdu, tohle je chyba. Chtěl jsem napsat pouze ten hook, bez balastu kolem, ale pak jsem si řekl, že tam radši dám i ten import, ale už jsem zapomněl hook obalit funkcí. Pokud je toto skutečně důvodem celé této výměny názorů, tak se omluvím a chybu samozřejmě opravím, ale z celé diskuze mi vůbec nevyplynulo, že by mohlo jít o zapomenutý kus kódu (i když mám pocit, že to nebude tak jednoduché). Díky moc za upozornění.
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.
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.
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:
Vlastní třída:
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:
A použití:
Použití na jeden řádek, žádná implementační logika „neprosakuje“ ven, žádný wrapper hell, prostě jen čistý a čitelný kód :-)
Nenechajte obmedzovat, navrhujem vam zajst s tou optimalizaciou a riesenim problemov este dalej: Zbavte sa aj funkcii. :)
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.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 :)
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.
Napište prosím, které konkrétní informace jsou špatně a proč. Pak se na to můžeme podívat.
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. :)
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.
neřešte ho Martine, je to slovák, který umí jen rejpat a nic neumí odůvodnit…
Obecně k věc:.
můj článek popisuje novou featuru hooks a základní použití třech z nich s tím, že vycházím z dokumentace, přednášek (nejen) členů React core týmu a vlastních zkušeností. Opakovaně Vás žádám o ukázku kódu, kde mi předvede, jak hooks a functional componenty škodí.
Teď k článkům, které jste sem vložil:
četl jste ty články? Nebo jen jejich nadpisy?
a doporučuje přejít na pattern Render Prop (AKA Function as Child), který některé z problémů řeší
(konflikty názvů injektovaných props, nejednoznačnost zdroje injektování…), ale wrapper hell zůstává. V době přednášky, ještě hooks, které tyto problémy řeší, neexistovaly
error boundaries stačí použít v podstatě na jednom místě v projektu a getSnapshotBeforeUpdate je pro specifické případy
(od vydání jsem getSnapshotBeforeUpdate, což je asi víc než 1,5 roku, použil snad jednou).
Pokud testy neřeší interní implementaci komponent, ale její veřejné API, jak by správně mělo být, tak není problém.
Některé testováci tooly, jako Enzyme, ještě pracují na ladění podpory například pro shallow rendering, což je skutečně dobré zmínit
Jiný komentující však upozornil, že dost možná toto celé odstartovala má chyba v kódu:
Ten hook skutečně nemá být na úrovni modulu, ale zapouzdřený do funkce. Chtěl jsem pouze naznačit použití hooku samotného, ale pak jsem si říkal, že tam radši ještě dopíšu i ten import, a už jsem zapomněl dopsat i funkci kolem něj. Je to chyba z nepozornosti, kterou opravím, ale pokud to byl skutečně zdroj tohoto celého, mohl jste mě na to upozornit rovnou.
Citali ste moje komentare? ;)
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.
Článek byl myšlen jako lehký úvod do problematiky s tím, že jsem uvažoval o pokračování a předvedení pokročilých konstrukcí. Článek má totiž omezený rozsah a rozebrat všechno naráz prostě nejde.
To s těmi třídami a „this“ (v kontextu JavaScriptu!) bych se Vám pokusil vysvětlit, ale cokoliv co napíšu, tak ignorujete a vezmete si z toho první odstavec, takže to vynechám.
Ukázku kódu tady asi neuvidíme, protože z Vašich komentářů mi je jasné, že Vám nedostatečné znalosti Reactu nedovolují ho vytvořit.
Děkuji Vám za vytvoření naprosto zbytečně toxické atmosféry a cíleného dehonestování mé osoby místo toho, abychom vedli konstruktivní debatu. Dál už na Vás nebudu reagovat, protože to nemá smysl.
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 :)
Dobrý den,
já jsem v první řadě pořád doufal, že to celé povede k porozumění obou stran, ale bohužel se tak nestalo. Když došly argumenty, začalo se útočit na osobní úrovni, čehož už jsem se nechtěl účastnit. Škoda.
Každopádně článek je pouze úvodem. Celá problematika kolem hooků je rozsáhlá a obnáší hlavně změnu mindsetu. Hodlám časem napsat pokračování, které se zaměří na konkrétní případy užití, které jsou pokročilejší.
Každopádně moc děkuji za vlídné slovo, které v této diskuzi těžko pohledat. :-)