neděle 16. července 2017

Funkcionální paradigma: React a Redux

V poslední době se stalo funkcionální programování tématem, o kterém se hodně mluví. Z akademické sféry přešlo do praxe a jak se ukazuje, tak je to způsob, který je v určitých oblastech zajímavou alternativou, nahrazující imperativní přístup.

Dnešní článek bude zaměřen na to, co to vlastně je ten funkcionální přístup. I když na toto téma existuje spousty článků, tak často je vše popisováno příliš abstraktně, což zapříčinilo, že ne každý chápe, o co vlastně jde.

Abych byl schopen demonstrovat tuto problematiku, zvolil jsem si k tomu dvě knihovny, které jsou navrženy podle funkcionálního paradigmatu (alespoň z části).

Funkcionální programování

Přesná definice říká, že se jedná o deklarativní programovací paradigma, které chápe výpočet jako vyhodnocení matematických funkcí.

Samotný program si poté můžete představit tak, že je složen z funkcí, které volají další a další funkce a tím se postupně zjednodušují. Pokud bychom hledali jazyk, který je jednou z vlajkových lodí tohoto přístupu, nejspíše bychom skončili u LISPu či Haskellu.

Druhou důležitou vlastností je to, že funkce nevytváří vedlejší efekt. Ve výsledku to znamená, že na stejný vstup vždy dostávám stejný výstup a nemodifikuje žádný stav. Toto je jedna z důležitých vlastností, kterou se dá odlišit funkcionální a objektový přístup.

Lambda funkce

Abychom byli schopni deklarativního programování, budeme k tomu potřebovat jednu zásadní věc a tím jsou lambda funkce.

Při mém prvním studiu toho, co to vlastně lamba funkce jsou, jsem skončil s plnou hlavou různých teoretických a abstraktních pojmů a stále jsem nechápal, co to vlastně znamená.

Postupně jsem si vytvořil jednoduchou pomůcku, která mi osvětlila tuto problematiku. Tou pomůckou je, že funkci mohu přiřadit do proměnné a tím se proměnná stává funkcí.

Zkusím uvést příklad:

const hello = function(param) {
    return `Hello ${param}`;
};
console.log(hello('World'));

Na tomhle příkladě je vidět, že jsme vytvořili proměnnou s názvem "hello",  která je funkcí.

Příklad je vcelku jednoduchý, tak si ho pojďme trochu rozšířit:

const hello = function(param) {
    return `Hello ${param}`;
};
const helloFromLanguage = function(param) {
    return param('from Javascript');
};
console.log(helloFromLanguage(hello));

Nyní si můžeme všimnout, že jsme vytvořili druhou funkci s názvem "helloFromLanguage", která jako parametr přijímá funkci, kterou sám zavolá.

Nyní si ještě tento příklad přepišme pomocí arrow functions z ES6:

const hello = (param) => `Hello ${param}`;
const helloFromLanguage = (param) => param('from Javascript');
console.log(helloFromLanguage(hello));

To je vše. Lambda funkce a deklarativní programování je vcelku jednoduchá záležitost. Na rozdíl od imperativního přístupu se dá říct, že stále dokola vytváříme callbacky, které se reálně vykonají jinde, než na místě, kde jsme je definovali.

Javascript a funkcionální programování

Pokud bychom o javascriptu mluvili jako o funkcionálním jazyce, tak bychom to museli dát do uvozovek. Přesnější definicí je, že Javascript je hybridní jazyk, ve kterém můžete programovat jak objektově, tak funkcionálně.

I když se často porovnává, zda je lepší objektový či funkcionální přístup, tak si s tím nemusíme lámat hlavu. Osobně si myslím, že se tyto dva přístupy mohou kombinovat a tím těžit z obou paradigmat.

React a funkcionální přístup

Psát o tom, že je React skvělá knihovna pro psaní frontendu, je asi zbytečné. Všichni, co to jednou zkusí a pochopí jeho fungování, tak už nechtějí jinak. Je to stejné, jako když si zvyknete na MacBook, ... ale o tom až jindy :)

V Reactu naleznete několik míst, které "přiznávají" funkcionální přístup.

Pojďmě si několik těchto míst ukázat....

Stateless komponenty

Stateless komponenty jsou funkcí a ničím jiným. Díky tomu nemají žádný interní stav a pokud danou komponentu budeme volat se stále stejnými parametry, budete vždy dostávat stejný výstup.

Pojďme si ukázat jednoduchou komponentu:

interface Props {
    title: string;
}
export const InboxDetailBlock: React.SFC<Props> = ({title, children}) => (
    <fieldset>
        <legend><FormattedMessage id={title}/></legend>
        {children}
    </fieldset>
);

Komponenta přijímá jako vstup props s "title" a "children". Sama nic nemodifikuje, nemá interní stav a pokud jí předáme stejný vstup, vždy bude stejný výstup.

React props

Druhým místem, který lze zmínit jsou samotné props. Props jsou uvnitř komponenty v immutable stavu a tím pádem žádná komponenta nemůže způsobit vedlejší efekt v podobě mutace vstupu. K tomuto účelu slouží callbacky, kterými oznamujeme, že se v komponentě "něco stalo".

Rozšíříme náš příklad o ukázku s callbackem:

interface Props {
    title: string;
    onClick: () => void;
}
export const InboxDetailBlock: React.SFC<Props> = ({title, children, onClick}) => (
    <fieldset onClick={onClick}>
        <legend><FormattedMessage id={title}/></legend>
        {children}
    </fieldset>
);

interface PropsDetail {
}
export const Detail: React.SFC<Props> = () => (
    <InboxDetailBlock title="My block" onClick={() => console.log('Clicked!')}>
        <p>Hello world!</p>
    </InboxDetailBlock>
);

Zde můžete vidět, že komponenta "InboxDetailBlock" má událost na kliknutí na fieldset. Tato událost je směrem k rodiči oznámena pomocí callbacku. Tím se eliminuje nutnost mutace jakéhokoli stavu, který by tuto událost vykonal. Tomuto přístupu se říká "one way binding", kde se událost oznamuje pomocí callbacku a nikoli mutací.

Redux jako globální stav

Pokud jsme se bavili o tom, že metody nemají vedlejší efekt, který by mutoval stav, tak přesně tímto způsobem funguje i Redux. Redux, jakožto implementace Fluxu, vychází z jednoduché myšlenky, která říká: "Máme pouze jeden zdroj pravdy (globální stav) a pokud ho chceme měnit, tak musíme vytvořit jeho kopii, nikoli ho mutovat."
Ve funkcionálním paradigmatu bychom totiž globální stav neměli nikdy mutovat, ale pouze vytvářet jeho nové kopie. Tedy změna stavu je tvorba nového stavu.
V Reduxu k tomuto účelu slouží takzvané reducery, což jsou jednoduché funkce, které přijímají aktuální stav a payload akce. Návratovou hodnotou je poté nový stav.
Jednotlivé React komponenty, které jsou na Redux state navěšeny, poté automaticky reagují na změnu stavu.
Díky tomuto přístupu máte v čase vždy pouze jeden stav. S nadsázkou se dá říci, že pro debugování vám často stačí sledovat redux state a díky tomu víte v jakém stavu je celá vaše aplikace.
Pokud svou aplikaci programujete podle těchto pravidel, tak se vám nemůže stát, že by React či Redux vytvářel vedlejší efekty a čímž vám dost sjednodušuje život.

Závěr

Ve funkcionálním přístupu naleznete spoustu zajímavých témat, viz třeba monády. Cílem tohoto článku nebylo vysvětlit celou problematiku, ale pouze nastínit, jak to vlastně s tímto paradigmatem je.
Funkcionální a deklarativní přístup není spásou pro vše. Nicméně zde je krásně vidět, že existují oblasti vývoje, kde se tento přístup může hodit a ba co víc, může být i hodně prospěšný.
Existuje ještě jedna hezká ukázka a tou je implementace GraphQL, kde pomocí deklarativního zápisu, přes callback "resolve", je velice snadné, aby za GraphQL API byl jakýkoli zdroj, který by byl velice jednoduše měnitelný. Každopádně o GraphQL se ještě něco dozvíte v následujících článcích...

Žádné komentáře:

Okomentovat

Když programátor založí a řídí firmu

Jako malý jsem chtěl být popelářem. Ani ne tak proto, že bych měl nějaký zvláštní vztah k odpadkům, ale hrozně se mi líbilo, jak...