V nedávném článku Tomáš Jílka jak psát velkou aplikaci v Reactu jsme vám představili způsob, jak o programování v Reactu přemýšlíme. Nyní bychom na toto téma rádi navázali a rozšířili jej o informace, proč používáme pro naše projekty v Reactu právě Redux.

V úvaze pokračuje Tomáš Vejpustek: „Může to znít zvláštně, ale já si myslím, že nejdůležitější věc, kterou nám přináší, je udržovatelnost, robustnost ke změnám. A udržovatelnost je na velkých a dlouhodobých projektech klíčová.“

Představte si velkou webovou stránku s mnoha prvky. Zákazník požaduje následující funkci: po stisknutí tlačítka v levém dolním rohu se schová nadpis nahoře. Jak to udělat? Pokud používáte jQuery, stačí kousek kódu, který přes ID najde tlačítko a nadpis a na tlačítko navěsí event handler, který schová nadpis. Věřím, že byste to zvládli do pěti minut. Úkol uzavřen.

Jenže po půl roce někdo změní ID tlačítka a tím celou funkcionalitu rozbije, protože nebude vědět, že musí zároveň změnit selektor v jQuery kódu. Dobře, to jde vyřešit. Kód můžeme umístit přímo pod tlačítko ve <script> tagu a každý, kdo bude editovat tlačítko, uvidí, že se k němu váže nějaký kód. Co když však někdo změní nadpis? Přece nemůžeme mít kód na dvou místech.

React je v tomto ohledu mnohem lepší. Zavedeme komponentu, která bude obsahovat tlačítko i nadpis. To, jestli bylo tlačítko stisknuto, se bude uchovávat v jejím stavu, který budeme předávat nadpisu. Když bude někdo přepisovat nadpis, uvidí, že tam je nějaký parametr shouldBeDisplayed. Zároveň tato komponenta vystaví event handler, který předá tlačítku. Pokud bude někdo přepisovat tlačítko, uvidí, že používá nějaký handler a musí s tím počítat.

„Může to znít zvláštně, ale já si myslím, že nejdůležitější věc, kterou nám přináší, je udržovatelnost, robustnost ke změnám. A udržovatelnost je na velkých a dlouhodobých projektech klíčová.“

Je zde však jeden problém: naše stránka je opravdu velká a mezi komponentou, která obsahuje tlačítko a nadpis a komponentami pro tlačítko a nadpis je ještě spousta dalších komponent (panelů, sekcí, odstavců). A handler i parametr musíte přes všechny tyhle komponenty předat.

Když se změní struktura stránky, musíte si dávat pozor, aby se vám někde neztratily, jinak schovávání nadpisu zase přestane fungovat. Navíc, pokud je tam takových funkcí více, musíte pro každou najít nějakou společnou komponentu, ve které bude stav týkající se této funkce.

A tak vás možná logicky napadle: dobře, co kdybychom dali veškerý stav do komponenty pro celou stránku, která je v DOMu úplně nahoře? A co třeba předávat handlery a parametry nějakým způsobem, o který se nemusíte starat v jiných komponentách (někteří z vás znají kontext)? Takhle bychom měli stav na jednom místě. Komponenty, které potřebují handlery nebo parametry o nich vědí a ty, které je nepotřebují, s nimi nemusí pracovat.

Zní to skvěle, co říkáte? Pokud vás tohle napadlo, vymysleli jste s popsali Redux.

Architektura

Redux je knihovna, která spravuje stav JS aplikace. V zásadě představuje model v klasickém model-view-controller schématu. To znamená, že potřebujete ještě nějakou knihovnu, která vykresluje uživatelské rozhraní. Nejčastěji se používá React (pro který byl Redux původně vytvořen), ale můžete použít také Angular nebo něco jiného. Pro účely tohoto článku se budeme zabývat především použitím s Reactem. Redux vychází ze starší architektury Flux, která byla původně vyvinuta pro použití s Reactem.

Redux zavádí jednosměrný tok informací:

  1. Základem je Store, který obsahuje veškerý stav aplikace (technicky lze použít i stav jednotlivých komponent, ale až na výjimky není důvod to dělat).
  2. Na stav jsou napojené UI komponenty. Ty se překreslí pokaždé, když se stav změní.
  3. Uživatel svou činností na stránce generuje události. UI komponenty na jejich základě vytváří akce (pomocí generátorů akcí).
  4. Akce jdou přes dispatch, který je předá do store.
  5. Uvnitř store se akce aplikují na stav pomocí reduceru.
  6. Na základě změny stavu se překreslí UI komponenty …

Při každé akci uživatele tedy projde aplikace tímto „kolečkem“.

Neměnnost

Redux si mnohé půjčuje z funkcionálního programování. Reducer je funkce bez vedlejších účinků, která dostane akci a stav a vrátí nový stav. Důležité na tom je, že stavový objekt je neměnný. Tím pádem se vytváří posloupnost stavů, ve které se dá vracet (undo/redo je zadarmo). Zároveň lze podstromy, které se nemění, sdílet mezi stavy, čímž se šetří paměť.

Neměnnost stavu není vynucována. V zásadě můžete pracovat s obyčejným JS objekty. Může se vám ale stát (a dříve nebo později se vám to stane), že při nějaké složitější operaci stav omylem změníte a pak budete dlouho hledat, co způsobilo ty divné chyby, co se vám objevují. Proto se doporučuje použít knihovnu Immutable, která vynutí neměnnost stavu a zároveň poskytuje pěkné funkce, které zjednoduší výpočet nového stavu.

Reducer a Selektory

Redux definuje pouze jeden store a jeden reducer (narozdíl od Fluxu). Často ale máte v aplikaci více domén, které jsou relativně nezávislé (například autentizace) a chtěli byste jejich stav spravovat zvlášť. Pro tyto účely existuje funkce combineReducers, která vám z více reducerů vytvoří společný reducer, který spravuje dva nezávislé podstromy (struktura stavu odpovídá struktuře reduceru).

Oddělovat domény je na větším projektu dobrá praxe, je však třeba dávat pozor na to, jak domény rozdělit. Komponenty můžou normálně obsahovat informace z více domén (takže můžete například zobrazit jméno současného uživatele a počet jeho článků vedle sebe). Jedna akce může měnit více domén zároveň, problém ale je, pokud mají být změny závislé (například nastavit současného uživatele jako autora vytvářeného článku). Většinou to jde vyřešit lepším rozdělením domén a nebo pomocí middleware, o kterém si něco povíme příště.

Je třeba se zaměřit i na čtení dat ze stavu. Aby komponenta mohla ze stavu načíst data, která potřebuje, musí znát jeho strukturu. Pokud však více komponent potřebuje stejná data, zavádíme do kódu redundanci, což nikdy není dobré (někdo změní strukturu stavu a zapomene změnit všechny komponenty). Z tohoto důvodu se zavádějí selektory, což jsou funkce, které získávají ze stavu informace. Důležité je, že jsou na jednom místě a pokud změníme strukturu stavu, stačí nám změnit selektory v jednom souboru.

Další výhoda selektorů je, že pokud některé používáme opakovaně, dají se jejich hodnoty cachovat (existuje výborná knihovna reselect).

Komponenty

Aby měly UI komponenty přístup k datům ze stavu, musíme je na něj napojit. V případě Reactu pak můžeme rozlišovat mezi dvěma druhy komponent, ze kterých se skládá uživatelské rozhraní:

  1. Hloupé komponenty, které pouze vykreslují to, co je jim zadáno v parametrech. Příkladem může být tlačítko, kterému předáme popisek a onClick event handler. Hloupá komponenta ale může být i tabulka, která vykreslí data na základě seznamu sloupců, řádků a několika event handlerů. Tyto komponenty jsou obyčejné komponenty v Reactu a neví nic o Reduxu a stavu.
  2. Chytré komponenty (také kontejnery), které čerpají data ze stavu aplikace. Příkladem může být seznam odkazů na články přihlášeného uživatele.

Chytré komponenty se často vytváří tak, že se hloupá komponenta napojí na stav. Když začnete s Reduxem, budete mít pravděpodobně tendenci vytvářet velké množství chytrých komponent. To je určitě správný přístup, protože jinak byste si museli vykreslovaná data předávat v parametrech (čemuž se chceme vyhnout). Jediný důvod, proč psát hloupé komponenty je, pokud je chcete použít na více místech. Dává tedy například smysl vytvořit hloupou komponentu pro tabulku nebo pro menu, kterou pak ale naplníte daty ze stavu. Důležité je dávat si pozor, aby hloupé komponenty nebyly moc komplikované, protože pak není těžké v nich udělat chybu.

„Chytré komponenty se často vytváří tak, že se hloupá komponenta napojí na stav.“

Z důvodu znovupoužitelnosti jsem toho názoru, že pokud hloupá komponenta obsahuje chytré komponenty, není už tak úplně hloupá. To je ale spíše věc názoru.

Závěrem

Redux používáme z toho důvodu, že nutí programátory mít související kód na několika málo definovaných místech. Když je pak třeba něco změnit, je jasné, kde všude je třeba změnu udělat, čímž zmenšíme pravděpodobnost zavedení chyb.

Může se zdát, že Redux zavádí pro programátorskou práci mnoho omezení (stavový objekt je neměnný, …). Nemyslím si ale, že je to jen daň za to, aby byl projekt v budoucnu udržovatelný, aby tím, že každý použije, co se mu zrovna bude hodit, neskončil v naprostém chaosu. Příliš velká svoboda a příliš velký výběr jsou těžké. Mnoho možností může znamenat bezradnost, kterou si vybrat. Omezení nám dává pevný rámec, ve kterém se snáze pohybuje. Přitom spousta zajímavých věcí vzniká tak, že se snažíme pracovat v rámci omezení a nějak je překonat. Proto zastávám názor, že dává smysl určit si na začátku projektu vhodná omezení a těch se držet. A Redux je jedno z vhodných omezení.

Pokud si chcete o Reduxu přečíst více, doporučuji oficiální dokumentaci v angličtině.


Tomáš Vejpustek

Tomáš je Java a JavaScript developer se zaměřením na front-end a UX. Zajímá se o využití výpočetní techniky k řešení každodenních problémů. Chce vytvářet taková uživatelská rozhraní, která by sám rád používal.

Máš rád front-end stejně jako Tom nebo ho chceš jako on ovládat?

Hledáme do naší party skvělé vývojáře – třeba právě na projekt eBay nebo React.js developra.

Mrkni taky na všechny pozice, co nabízíme a dej nám o sobě vědět, bez ohledu na to, zda máš rád front-end nebo back-end, jestli nějakou technologii umíš nebo ne. Pokud budeš ty sám chtít, dostaneš u nás příležitost naučit se, co tě zajímá nebo udělat něco výjimečného.