48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/pt.2-React/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Naučte se React.js 1/3 - React pt.2
4 | Tak v minulé části jsme si vyvětlili a ukázali, proč není super nápad používat jQuery styl na velká UI. Nebyl to hejt na knihovnu jQuery, byl to hejt na styl, jakým se používá na tvorbu dynamického UI.
5 |
6 | Klasická HTML šablony implementované v Javascriptu se pro takový úkol hodí mnohem více, ale jejich problém tkví v tom, že jsou jenom parsovače stringu používající `String.replace()` a nic víc. Nepomáhají nám psát lepší a bezpečnější kód.
7 |
8 | Nicméně přístup vytváření HTML je cestou, kterou se vydat, daleko lépe se hodí pro tvoření velkého UI.
9 |
10 | Jako poslední jsme si ukázali styl, kdy píšeme celé UI v Javascriptových funkcích a tahle cesta vyřešila všechny problémy jQuery přístupu či javascriptových HTML šablon.
11 |
12 | Narazili jsme ale na neduhy a k jejich vyřešení potřebujeme splnit tyto body:
13 |
14 | - dokopat Javascript ke kontrole typů argumentů, chodících do šablon
15 | - zefektivnit vykreslování šablony - aby se upravilo jen to, co je potřeba a udržovaly se instance inputů, aby neztrácely focus apod.
16 | - přidat možnost psát šablony v přívětivějším duchu tak, aby kodéři neprskali
17 |
18 | Asi vás nepřekvapí, že všechny tyhle problémy vyřeší knihovna React za nás. Jdeme na to.
19 | ## Zázraky funkcí
20 | Ještě než se vrhneme na samotný React, tak bych se chtěl vrátit k našemu přístup s čistým Javascriptem. Slíbil jsem, že si ukážeme, jak jednoduché je si napsat serverové renderování, pokud máme čisté funkce. No a co jsem slíbil, to dodržím!
21 |
22 | Tak pokud se podíváte na kód [zde](../pt.1-jQuery-to-React/javascript/server.html) a spustíte si ho v prohlížeči, tak si všimnete prostě textu, který se vypíše jenom na klientu tj. HTML, které přichází ze serveru je prázdné, je tam pouze jeden `` kam se renderuje aplikace. Renderování probíhá jenom na klientovi.
23 |
24 | No a když se podíváte [sem](../pt.1-jQuery-to-React/javascript/server.js) tak tady je kód pro spuštění klasického HTTP serveru v Node.js, který zatím nedělá nic. Spustíte si ho příkazem:
25 | ```bash
26 | yarn server
27 | ```
28 | Na url [http://localhost:9999](http://localhost:9999) vám nyní běží ten zázrak. Vypíše se pouze nějaký text, nic zvláštního.
29 |
30 | No ale pokud vezmu náš známý kód, který přes objekt `DOM`, který přes metody `DOM.div(), DOM.span() ...` vytvářel DOM elementy a zkopírujete ho do `server.js` a změníte dvě řádky, tak uvidíte, jaké se budou dít věci...
31 |
32 | Stačí jenom upravit tyhle řádky:
33 | ```js
34 | var DOM = createDOM(document)
35 | // na:
36 | var DOM = createDOM(serverDocument)
37 |
38 | // a
39 | res.send(html.replace('{HTML}', 'SERVER'))
40 | // na:
41 | res.send(html.replace('{HTML}', renderApp(initialState)))
42 | ```
43 | Restartujeme server a voilá!
44 |
45 | To bylo snadné, co? Zkuste si tohle udělat se nějakou Javascriptovou šablonou, jestli to pude tak snadno.
46 |
47 | Asi vás nepřekvapí, že téhle jednoduchosti využívá React a je schopný svoje šablony vyrenderovat i na Node.js serveru!
48 |
49 | To znamená, že SEO netrpí a aplikace nečeká na to, až stáhne Javascript, aby vůbec něco zobrazila, skvělé!
50 |
51 | ## Hurá na ten React
52 | Takže už opustíme do Reactu jako takového.
53 |
54 | ### Těžké začátky...
55 | Určitě jste zaregistrovali články jako slavné Javascript Fatique nebo vtípky na téma, že při hackathlonu se povedlo nainstalovat pouze Babel, takže se nikdo ani k Reactu nedostal.
56 |
57 | Jak to tak chodí, všechno to jsou takové polopravdy. React se dá použít stejným stylem jako jQuery, byť to opravdu je daleko od nějaké produkční konfigurace.
58 |
59 | Nicméně jsme skončili někde [zde](./basic.html). V tomhle příkladu renderujeme dvě úrovně nadpisu a k tomu ještě ukážeme `input`, který upravuje hlavní nadpis. Myšlenka byla taková, že při každém stisknutí klávesy se má aktualizovat nadpis podle toho, co je v inputu. To sice funguje, ale input ztrácí focus. Ten důvod je jasný, naše primitivní implementace není schopná udržet instance vyrenderované komponenty, takže ji pořád nahrazuje.
60 |
61 | Stejně tak mrháme výkonem, protože přerenderováváme celou aplikaci, ačkoli to je naprosto zbytečné, potřebovali bychom v DOMu aktualizovat jen ty kousky, které se změnili.
62 |
63 | Myslím, že tedy pohřbíme vlastní implementaci a vrhenem se na něco, co je už daleko víc vyvoněné a funkční no a to je React.
64 |
65 | #### Jak to rozjet
66 | Jak ho ale naroubujeme na naší implementaci DOMu? No, věřte tomu nebo ne, je potřeba do naší implementačky přidat nahoru jen pár řádek a pár jich smazat.
67 |
68 | V podstatě jde jenom o to nahradit:
69 | ```js
70 | var DOM = createDOM()
71 |
72 | // Reactí
73 | var DOM = React.DOM
74 |
75 | // a místo
76 | renderApp(dom, appElement)
77 |
78 | // použít funkci z Reactu
79 | ReactDOM.render(React.createElement(app, defaultData), appElement)
80 | ```
81 | Abychom získali všechny globální proměnné, které jsou potřeba, stačí pouze jenom nakopčit do hlavičky klasickým stylem pár scriptů. Jsou to tyhle:
82 | ```html
83 |
84 |
85 |
86 |
87 |
88 | ```
89 | No a pokud jste vše pečlivě nahradilim měla by se vám tedy v souboru [basic.html](./basic.html) zobrazit to samé, co bez Reactu.
90 |
91 | Takže co, je to teda těžké nebo ne?
92 |
93 | ### Co ten React vlastně je
94 | Trošku jsme se vykašlali na teoretickou část, tak co ten React vlastně je?
95 |
96 | No tak srovnání s jQuery je dost nefér, neboť jQuery je knihovna, která toho svede mnohem více než React.
97 |
98 | React totiž dělá jenom jednu věc a dělá ji poměrně dobře - šlo by to lépe. React je pouze knihovna pro tvorbu DOMu a jeho efektní modifikaci. Nic víc ni míň.
99 |
100 | Reactem tedy sám o sobě neumí:
101 | - request na server
102 | - parsování cookies
103 | - routování
104 | - modelovat logiku aplikace
105 |
106 | React se tedy soustředí na jednu jedinou část v procesu psaní SPA aplikace a to je view - DOM a jeho chytré aktualizování. Proto se React nedá srovnávat ani s Angularem. Neboť Angular je programovací rámec aplikace - framework, v podstatě tu aplikaci píšete "v mezích" nebo "uvnitř" Angularu. Kdežto React je opravdu jenom knihovna pro vypisování HTML. Pokud tedy budete chtít psát nějakou velkou aplikaci, tak se zřejmě neobejdete bez něčeho jako je Angular.
107 |
108 | Například v Avocode jsme si postavili vlastní "framework" byť si teda myslim, že ten náš "framework" je tak jednoduchý, že se o frameworku opravdu nedá mluvit.
109 |
110 | Samozřejmě okolo Reactu vyrostlo nějakolik přístupů jak tvořit aplikace a některé z nich jsou opravdu revoluční, třeba Flux arichtektura a její implementace Redux. Nebo úžasně jednoduché MobX. O těhle srandách se brzy dovíte.
111 |
112 | Teď ale bychom si měli říct, jak přemýšlet "React way" a tak si zkusíme napsat pár jednoduchostí.
113 |
114 | ### Zvláštnosti
115 | Opravdu divnost Reactu, která je taková no, jak to vyjádřit. Prostě každej si toho všimne a začne se tomu vysmívat, asi proto neexistuje výraz. Prostě něco, čeho si všimnete jako prvního, vysmějete se tomu, zavřete tab prohlížeče a napíšete o tom posměšný tweet. Tak taková věc je v Reactu JSX.
116 |
117 | Co to je? Toto:
118 | ```js
119 | const renderButton = (props) => {
120 | return (
121 |
122 | )
123 | }
124 | ```
125 | Říkáte si WTF? Jakože HTML v Javascriptu? Tak je čas opustit přednášku, napsat posměšný tweet :)).
126 |
127 | Tohle má svoje důvody.
128 |
129 | Pamatujete si, jak jsme se bavili o tom, že psát UI, jehož výstupem je HTML v čistých javascriptových funkcích je prostě nečitelné a nepřehledné? To samé si řekli vývojáři Reactu a vymysleli tuhle syntax - JSX.
130 |
131 | V první řadě bych chtěl podtrhnout a skoro vykřiknout, že tohle **není** HTML. Neboť HTML je statický značkovací jazyk. Zatímce JSX v podstatě je Javascript. To, že napíšete:
132 | ```js
133 | return (
134 |
135 | )
136 | ```
137 | Říkáte: Zavolej funkci `button` s argumetem `{ className: 'btn btn-success', children: 'Click me' }`.
138 |
139 | Nic víc!!! Navíc tenhle kód nejde ani spustit v prohlížeči (jde to, ale není to dobrý nápad dělat v produkci), tohle je čistě pseudosyntaxe, syntaxsugar nad Javascriptem. Nic víc nic míň. Bylo to vynalezeno právě proto, aby kodéři neprskali a měli se čeho chytit. Hlavně prosím prosím, neříkejte HTML v JavaScriptu nebo mi vybuchne hlava :)).
140 |
141 | Takže co se jako s timhle kódem stane, aby šel pustit v prohlížeči?
142 |
143 | Před:
144 | ```js
145 | const renderButton = (props) => {
146 | return (
147 |
148 | )
149 | }
150 | ```
151 | Po kompilaci:
152 | ```js
153 | const renderButton = (props) => {
154 | return (
155 | React.createElement('button', {
156 | 'className': "btn btn-success",
157 | 'children': 'Click me',
158 | })
159 | )
160 | }
161 | ```
162 | Takže žádné HTML, jenom funkce, která vrací DOM element - Javascriptový objekt, žádný HTML string.
163 |
164 | Abychom si vyzkoušeli JSX, tak klidně můžeme použít `standalone babel transpiler` rovnou v prohlížeči.
165 |
166 | Tohle je jenom na hraní! Ten transpiler je obrovský, takže to v produkci nemá co dělat. Pro produkci je nejlepší si kód prostě hezky zkompilovat a připravit, né to cpát prasácky jako inline skripty bez explicitní závislosti!!!
167 |
168 | Ukázku JSX přímo v prohlížeči si můžete prohlédnout zde a rovnou začnem psát naší styleguidovou apku, tentokrát už zcela v Reactu.
169 | ### Základy React API
170 | #### Tvorba komponent a props
171 | React API je snadné. Můžete si vybrat, jestli psát komponenty jako funkce:
172 | ```js
173 | const renderButton = (props) => {
174 | return (
175 |
176 | )
177 | }
178 | ```
179 | Nebo jako třídy.
180 | ```js
181 | class Button extends React.Component {
182 | render() {
183 | return (
184 |
185 | )
186 | }
187 | }
188 | ```
189 | V případě třídy nám `props` tj. argumenty předané takto:
190 | ```js
191 |
192 | ```
193 | Přicházejí jako `this.props`.
194 | Tudíž, zavoláme-li třídu `Button` takto:
195 | ```js
196 | const renderButton = (props) => {
197 | return (
198 |
199 | )
200 | }
201 | ```
202 | Tak uvnitř této třídy budu mít dostupné:
203 | ```js
204 | this.props = {
205 | className: 'btn btn-success',
206 | onClick: naKlik,
207 | }
208 | ```
209 | Takže důležitý pojem jsou `props`. To jsou parametry předané komponentě, v případě funkce je jasné že jsou jako první argument funkce. Props jsou vždy objekt.
210 |
211 | #### Stav komponent - state
212 | Pokud předáváte jenom props, tak nejste schopni nic měnit za běhu - aplikace prostě jenom vyrenderuje, nic víc, není možné nic změnit.
213 |
214 | Na změnu stavu komponenty používáme metodu `this.setState()`.
215 |
216 | Tahle metoda bere jako argument funcki nebo objekt. Jednodušší je předat objekt.
217 |
218 | `this.setState()` je možné logicky použít jenom u komponent, které jsou vytvořené jako třídy. Takže můžeme udělat třeba jednoduché počítadlo kliknutí.
219 |
220 | Zdrojá si prohlédněte [zde](./counter.html).
221 | ```html
222 |
247 | ```
248 | Pozor, tady spouštíme `JSX` v prohlížeči - znovu opakuji, prohlížeč nedokáže spustit `JSX`. Proto musíme použít prasárnu a transpilovat přímo v prohlížeči, všimněte si proto `
251 | ```
252 | Nepoužíváme `text/javascript` ale `text/babel` a k tomu ještě divoká nastavení přes `data-presets`. Zatím netřeba řešit proč to tak je, stačí zkopčit a jet. Nezapomeňte na všechny skript z hlavičky!! Jinak vám to nepojede.
253 |
254 | Tak tohle jsou opravdové základy Reactího API, je toho víc. Zbytek se dozvíte od Petra Brzka a Jirky Vyhnálka.
255 |
256 | Teď ale k naší styleguidové apce.
257 | ### Create React app
258 | Je opravdu blbej nápad používat všechny skripty inline a transpilovat babelem přímo v prohlížeči, je to pomalé a stejně to v produkci nikdy nepoužijete, leda byste byli... No... Ale radši nechci ani řikat kdo.. :))
259 |
260 | V repu jsem připravil takovou ultimátní hračičku, která se jmenuje `CRAP` neboli `create-react-app`. To je opravdu ta nejjednodušší cesta jak rozjet apku v Reactu. No a pak se můžete smát těm haterům, kteří nikdy nerozjeli Babel :).
261 |
262 | Takže stačí napsat v rooto tohodle repa. (samozřejmě po instalaci NPM balíčků)
263 | ```bash
264 | node_modules/.bin/create-react-app moje-aplikacka
265 | ```
266 | Co to udělá? No prostě to vytvoří složku `moje-aplikacka` a když se pak do ní nastavíte, tak můžete jednoduše spustit
267 | ```bash
268 | npm start
269 | ```
270 | A rozběhne se vám reactí apka se všema možnejma vychytávkama, aniž byste hnuli prstem... Zdrojáky jsou samozřejmě hezky dostupné v `moje-aplikacka/src` a jakmile je upravíte a uložíte, tak se stránka refreshne bez práce.
271 |
272 | Navíc máte k dispozici JSX a vůbec všechno a pěkně to funguje. Takže si jdeme hrát!
273 |
274 | Přepíšeme Aspoň dvě styleguidové komponenty do pravých reactích komponent! Jdeme na to!
275 |
276 | Inspirace je [zde](./index.html).
277 |
--------------------------------------------------------------------------------
/pt.2-React/basic.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | Javascript only
14 |
15 |
16 |
17 |
18 |
19 |
76 |
77 |
--------------------------------------------------------------------------------
/pt.2-React/counter.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | React!
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/pt.2-React/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | React!
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
191 |
192 |
193 |
--------------------------------------------------------------------------------
/pt.2-React/text.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | React!
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/pt.3-React-prakticky/README.md:
--------------------------------------------------------------------------------
1 | # React prakticky
2 |
3 | ## Teoretická část a prezentace
4 |
5 |
6 |
7 |
8 |
9 | **[Prezentace React prakticky](https://docs.google.com/presentation/d/1yuErzAASiDOWwvsODUVOPF1E8Xb6ji8LBqbcVSLp2zw/edit?usp=sharing)**
10 |
11 |
12 | ## Praktická část
13 |
14 | V praktické části práce jsme vyvíjeli aplikaci pro vytváření flashcards. Zdrojový kód a funkční specifikaci appky najdete v samostatném repozitáři [react-workshop-flashcards](https://github.com/webdev-js-evenings/react-workshop-flashcards).
15 |
16 | ## Zajímavé odkazy týkající se Reactu
17 |
18 | - [React patterns, techniques, tips and tricks](https://github.com/vasanthk/react-bits)
19 | - [JS.coach - vyhledávání React balíčků](https://js.coach/react)
20 | - [Awesome React - vyčerpávající list všeho skvělého pro React](https://github.com/enaqx/awesome-react)
21 |
--------------------------------------------------------------------------------
/pt.4-React-sprava-stavu/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "react-app",
3 | "rules": {
4 | "semi": 0
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/pt.4-React-sprava-stavu/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env
15 | npm-debug.log*
16 | yarn-debug.log*
17 | yarn-error.log*
18 |
19 |
--------------------------------------------------------------------------------
/pt.4-React-sprava-stavu/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Naučte se React.js 5 - na Redux
4 | V téhle části si **pomalu** vysvětlíme proč se používá v React to, co se používá a proč to né zcela pasuje na možné známější backendové aplikace.
5 |
6 | Prostě si opravdu napíšeme vlastní plnohodnotný balíček pro práci se stavem, vlastně takový Redux.
7 |
8 | OK, né zas tak rychle. V první řadě si je třeba ukázat v čem je problém.
9 |
10 | S Petrem jsme si konečně napsali docela pěknou aplikačku, která vám třeba pomůže v učení se té hrozné haldy pojmů okolo Javascriptu, že?
11 |
12 | Takže se dá říct, že práce s komponentami už tak nějak zvládáme a můžeme se vrhnout na to trošku komplikovanější a to je konečně celá aplikace.
13 |
14 | ## Co je to aplikace
15 | Každá aplikace se skládá ze tří částí, které můžou mít různé názvy, ale v zásadě se ustálilo označení:
16 | * M - model
17 | * V - view
18 | * C - controller
19 | ---------------
20 | * S - service
21 |
22 | Věřte tomu nebo ne. Ekosystém okolo Reactu též ctí toto uspořádání, ačkoli se dá hovořit o nějakém to [CQRS](https://martinfowler.com/bliki/CQRS.html) nebo co to je.
23 | Každopádně bych se rát pohyboval v přátelštějších prostředí než se koukat do nějakého šíleného computer science.
24 |
25 | Pod čáru jsem ještě přidal zmínku o `Service`. Prostě servisy. Takový typ "třídy" se v aplikacích též hojně vyskytuje a má celou řadu vyjímečností...
26 |
27 | ### Jak se co nazývá v Reactu
28 | Když jsme psali Reactí komponenty, tak bylo zvykem mít tu velkou root komponentu naplněnou stave `state`. V takovém případě takováto komponenta vlastně
29 | zastává práci modelu a zároveň i kontroloru. Může dokonce zastávat i funkci view, pokud celá šablona bude vypsána v téhle jedné komponentně.
30 | To ale jistě není rozumnější.
31 |
32 | Takže narvat si tři části aplikaci do jedné "šablony" je samozřejmě zvláštní nápad, protože jasně mícháme něco, co má zcela jiné odpovědnosti, že? Takže se pustíme do rozsekávání nějaké masivní komponenty plné stavu.
33 |
34 | Kde začít? Nejsnažší je pro začátek vzít veklkou komponentu, která vykresluje všechno a udělat z ní "kontroler" ve terminologie Reactu z ní můžeme udělat Root komponentu nebo "chytrou" komponentu, která drží handlery callbacků a stav.
35 |
36 | Takže můžeme z ní udělat třeba něco takovéhleho:
37 | ```js
38 | export default class StatefulComponent extends Component {
39 | state = {
40 | todos: [
41 | { id: 1, text: 'Nákup pro maminku na SváTek maTek' },
42 | { id: 2, text: 'Nakoupit na víkendovou kalbu 2 kila lososa rozpejkací bagetky' },
43 | ],
44 | user: {
45 | name: 'Vojta',
46 | email: 'vojta.tranta@gmail.com',
47 | id: 666,
48 | },
49 | time: Date.now(),
50 | formValue: '',
51 | }
52 |
53 | _handleTick = (time) => {
54 | this.setState({
55 | time,
56 | })
57 | }
58 |
59 | _handleFormChange = (nextValue) => {
60 | this.setState({
61 | formValue: nextValue,
62 | })
63 | }
64 |
65 | _handleFormSubmit = () => {
66 | this.setState({
67 | todos: [{
68 | 'id': Date.now(),
69 | text: this.state.formValue,
70 | }].concat(this.state.todos),
71 | formValue: '',
72 | })
73 | }
74 |
75 | render() {
76 | return (
77 |
95 | );
96 | }
97 | }
98 |
99 | ```
100 | Je to jednoduché, místo toho, aby komponenta renderovala všechno, tak si vybere jen pár elementů a pak je vypíše, předá jim správná props a povídá si s nimi přes callbacky v props.
101 |
102 | Takováto architektura může připomínat klasický `Presenter` nebo `Controller`, který můžete znát z jiných jazyků či frameworků:
103 | ```php
104 | class Controller extends BaseController {
105 | function __construct($todoFacade, $context) {
106 | $this->todoFacade = $todoFacade;
107 | $this->context = $context;
108 | }
109 |
110 | public function index($request) {
111 | return $this->_createResponse($request, 'templates/index', [
112 | 'page_title' => 'Index page!',
113 | 'todos' => $this->todoFacade->getTodos($request.get('page')),
114 | ]);
115 | }
116 | }
117 | ```
118 | Tohle je příklad v PHP. Tak nějak vypadá nějaký typický kontroler, který vykresluje indexovou stránku pomocí šablony, který je umístěná v `templates/index`. Data do ní cpe z nějaké databázové fasády `TodoFacade`.
119 |
120 | Podobně funguje i rootová reaktí komponenta. Sebere data a narve je do šablony. Šablony jsou v tomto případě nějaké nižší jednodušší komponety, které z pravidla nedrží stav a jen využívají props.
121 |
122 | Tedy závěrem můžeme jednoduše říci, že "chytrá" komponenta tedy taková, která má stav a realizuje callbacky z potomků a rozdává do nich props je vlastně takový controller tj. v MVC zaujímá písmenko C.
123 |
124 | Hloupé komponenty jsou naproti tomu takové, která pouze přímají props a přes callbacky si povídají se svými předky. Předkům jsou tudíž plně podrobeny. Můžeme o nich tedy říct, že jsou to views, tedy V v MVC struktuře.
125 |
126 | ### A co "M"?
127 | M je tedy model, jak se řeší modelová vrstva v reactových aplikacích?
128 |
129 | Pomůžeme si zase příkladem ze známého světa PHP. Máme-li formulář, kde je například vypsán uživatelův profil, tak tento formulář často odpovídá jedné řádce tabulce v databázi, která se typicky jmenuje `user` nebo `user_profile`. Jakmile zde upravíme nějaká data, a odešleme formulář, tak se celá stránka překreslí a my vidíme nový stav uživatelova profilu, jasné jako facka?
130 |
131 | Tohle v SPA aplikacích ale samozřejmě nechceme. Proč bychom kvůli změně jednoho políčk překreslovali celou stránku, když by stačilo změnit pouze jednu hodnotu inputu, že.
132 |
133 | Jak to tedy udělat. Budeme tedy mít nějakou instanci modelu `User` a všechny komponenty na ní budou poslouchat, jak se mění, že?
134 |
135 | No, tak to úplně není. Každý model by musel být v tom případě nějaký event emitter a museli bychom ručně registrovat nebo odregistrovávat posluchače na tuto jednu instanci, která se navíc může v průběhu času měnit.
136 |
137 | To by bylo trošku šílené.
138 |
139 | Samotnout ideu modelu už v sobě mají Reactí komponenty zabudovanou. Je to property `state` a metoda `setState()`.
140 |
141 | Zajímavé na tom je, že React se nesnaží vytváře nějaký Event emitter, vůbec se s ním v Reactových komonentách nesetkáte.
142 |
143 | Pokud chcete změnit stav, prostě zavoláte `setState()` a to je všechno. Komponenta se sama překreslí a tím pádem aktualizuje i všechny svoje "děti", které vykresluje v metodě render.
144 |
145 | Samozřejmě tohle není ideální a perfektní řešení z hlediska výkonu, ale z hlediska jednoduchosti není co vytknout, potřebuju-li změnit stav, tak to prostě udělám.
146 |
147 | Model je tedy vyřešený?
148 |
149 | ### View != Model
150 | Už staří Egypťané ve svých hyeroglifech varovali před tímto:
151 | ```php
152 |
156 |
157 |
158 |
161 |
162 |
163 |
164 | $value): ?>
165 |
= $value ?>
166 |
167 |
168 |
169 |
170 |
171 | ```
172 | Cítíte tu zrůdnost v tomhletom?
173 |
174 | No zkrátka, není správně dávat logiku (logika != Javascript) - myšleno to, jak se data tahají apod, a view (šablony) na stejné místo.
175 |
176 | Tedy přichází na scénu separation of concerns. React je knihovna pro tvorbu UI, není to knihovna pro správu dat.
177 |
178 | Jen abychom si rozuměli. Není špatné mít pár klíču ve `state` aplikace, ale je špatně mít tam stav celé aplikace - pokud už je aplikace velká. Stejně tak není rozumné mít v root komponentě všechny metody pro jeho změnu. Například není důvod, aby proces přihlášení uživatele byl v komponentě `Homepage`, může být v `LoginForm`, ale tam zase není radno dávat nějaký http requesty a podobně.
179 |
180 | Věcím je třeba dát řád podle toho, čeho se týkají a nemotat jablka a hrušky nebo skončíte u PHP a tam už jsme byli...
181 |
182 | ## Model? Stav!
183 | React tak nějak přišel s myšlenkou, že bychom se na aplikace měli koukat čistě jako na funkci stavu:
184 | ```
185 | aplikace = šablona(stav)
186 | ```
187 | A to je vše, nic víc není potřeba.
188 |
189 | A co je stav? No stav je pouze soubor klíčů je to v javascriptové terminologii objekt, který má klíče různých typů, u naší komponenty to vypadá takto:
190 | ```js
191 | state = {
192 | todos: [
193 | { id: 1, text: 'Nákup pro maminku na SváTek maTek' },
194 | { id: 2, text: 'Nakoupit na víkendovou kalbu 2 kila lososa rozpejkací bagetky' },
195 | ],
196 | user: {
197 | name: 'Vojta',
198 | email: 'vojta.tranta@gmail.com',
199 | id: 666,
200 | },
201 | time: Date.now(),
202 | formValue: '',
203 | }
204 | ```
205 | A to je všechno, celá aplikace. Nic víc není potřeba k jejímu zobrazení, všechny informace jsou zde obsažené.
206 |
207 | Tedy stav je vlastně objekt a proč bychom ho museli mít přímo v jedné komonentě, nešlo by ho prostě vytáhnout ven?
208 |
209 | O co přijdem, pokud to uděláme? No přijdeme o možnost ho měnit přes `setState()` tak ho tedy necháme nadále obaleného v komponentě. Jaké budou benefity?
210 |
211 | No můžeme vytvořit dekorátor:
212 | ```js
213 | const createApp = (AppComponent) => (state) => {
214 | return class extends React.PureComponent {
215 | state = state
216 |
217 | _updatState = (stateUpdate) => {
218 | this.setState(stateUpdate)
219 | }
220 |
221 | render() {
222 | const props = {
223 | ...this.state,
224 | updateState: this._updatState,
225 | }
226 |
227 | return
228 | }
229 | }
230 | }
231 | ```
232 | Dekorát je pouze funkce, která bere Reaktí komponentu jako argument a vrací funkci, která očekává stav jako argument a následně vrátí novou Reactí komopnentu, která je vlastně chytrá komponenta, která je ale už naprosto generická. Výhoda je taková, že už v žádné další komponentně nebude muset být schyzma mezi props a state, vše jsou prostě props.
233 |
234 | Použití takovéhoto dekorátoru pak vypadá následovně:
235 | ```js
236 | export default createApp(StateLessComponent)({
237 | todos: [
238 | { id: 1, text: 'Nákup pro maminku na SváTek maTek' },
239 | { id: 2, text: 'Nakoupit na víkendovou kalbu 2 kila lososa rozpejkací bagetky' },
240 | ],
241 | user: {
242 | name: 'Vojta',
243 | email: 'vojta.tranta@gmail.com',
244 | id: 666,
245 | },
246 | time: Date.now(),
247 | formValue: '',
248 | })
249 | ```
250 | Výsledek této funkce je Reactí komponenta, která předala do `StateLessComponent` celý zde nadefinovaný stav a k tomu ještě funkci `updateState`, která je vlastně `setState()` a to je vše.
251 |
252 | Takže aplikace funguje dál, jen stačí všude smazat `this.state` a nahradit ho za `props` a `this.setState()` můžeme nahradit za prostou funkci `updateState()`.
253 |
254 | Výsledná původní `StatefulComponent` je nyní mnohem jednodušší a přejmenovaná na `StatelessComponent`:
255 | ```js
256 |
257 | const StateLessComponent = ({ ...state, updateState }) => {
258 | const _handleTick = (time) => {
259 | updateState({
260 | time,
261 | })
262 | }
263 |
264 | const _handleFormChange = (nextValue) => {
265 | updateState({
266 | formValue: nextValue,
267 | })
268 | }
269 |
270 | const _handleFormSubmit = () => {
271 | updateState({
272 | todos: [{
273 | 'id': Date.now(),
274 | text: state.formValue,
275 | }].concat(state.todos),
276 | formValue: '',
277 | })
278 | }
279 |
280 | return (
281 |
299 | )
300 | }
301 | ```
302 | Ok, takže stav máme mimo komponentu, teď ještě jeho změny.
303 |
304 | ### Sémantické a nesémantické callbacky
305 | Asi jsem jediný na světě, kdo této záležitosti dal tak debilní jméno. Jaké záležitosti?
306 |
307 | Podívejme se na tyto dvě komponenty:
308 | ```js
309 |
310 | const Form = ({ children, onSubmit }) => (
311 |
315 | )
316 |
317 | const UserForm = ({ onRegisterUserReqest }) => {
318 | let input
319 | const _handleFormSubmit = () => {
320 | const userName = input.value
321 | onRegisterUserReqest(userName)
322 | }
323 |
324 | return (
325 |
326 |
Registration:
327 |
331 |
332 | )
333 | }
334 | ```
335 | Co konkrétně tyto komponenty dělají není příliš podstatné. Jen si ale všimněme, že komponenta `Form` bere callback `onSubmit`. To je prostě obecný callback, pokaždý, když se formulář submitne, tak se tento callback zavolá a je jedno, jaká data jsou ve formuláři, jestli to je formulář pro přidání todo a nebo formulář pro přidání nového uživatele.
336 |
337 | Zatímco "chytřejší" komponent `UserForm` bere callback `onRegisterUserRequest`. Tenhle callback už podle názvu značí co se daty bude dít po jejich odeslání. Ale ve své podstatě je to prostě jenom další `onSubmit` callback a nic víc.
338 |
339 | Já tedy takovýmto callbackům říkám `sémantické` neboť mi v aplikaci řeší nějkaou byznys logiku. Zatímco `nesémantické` callbakcy jako třeba `onSubmit`, `onClick`, `onFocus` mi neříkají nic o tom, co se má stát, když se zavolají, prostě bylo na něco kliknuto nebo něco bylo submitnuto, nic víc.
340 |
341 | Důvod, proč tyhle callbacky odlišuji je ten, že `sémantické` callbacky by se měly vyskytovat jen na úrovni `sémantické` komponenty. Zatímco jejím hloupým dětem (ahoj mami) by mělo bejt šumafuk, co se děje s daty, která odesílají či přijímají.
342 |
343 | A co je sémantická komponenta? Však to je jednoduché.
344 | Tohle je například nesémantická komponenta:
345 | ```js
346 | const ListItem = ({ title, body, children, id, onClick }) => (
347 |
348 | #{id}
349 | {title}
350 |
body
351 | {children}
352 |
353 | )
354 | ```
355 | A tohle je sémantická:
356 | ```js
357 | const TodoItem = ({ todo, onTodoClick }) => {
358 | return (
359 | onTodoClick(todo)}
364 | >
365 | Created on {todo.created}
366 |
367 | )
368 | }
369 | ```
370 | Je patrný rozdíl mezi těmito komponentami? Jedna pracuje s obecnými daty, druhá, ta sémantická, renderuje konkrétní data do požadované podoby a případně usměrňuje callbacky a vrací do nich přínosná data, napříkald do `onTodoClick` vrátí celý objekt `todočka`, zatímco nesémantická komponenta by prostě jenom vrátila klasický `onClick` s argumentem `e: Event`.
371 |
372 | ### Proč to vyprávím?
373 | Je důležité si totiž uvědomit, kde končí "chytrý" kód, který implementuje business logiku a tudíž není univerzální a kód, který už je obecný, znovupoužitelný. To vám umožní zobecňovat koncepty a ušetřit si práci a aplikaci zjednodušit.
374 |
375 | Co je ale mnohem důležitější je to, že tyhle sématické callbacky dělají vaší aplikací smysluplnou. Proto jsem psal, že tyhle sémantické callbacky definují business logiku a jejich propojení se stav je skutečné jádro aplikace. Není, není to stav aplikace jako takový, ale **cesta jakou se stav mění**.
376 |
377 | To je teď největší slabina naší aplikace, změnu stavu je totiž definována pouze jako sémantický callback v "chytré" komponentě, který ačkoli je umístěný v komponentě přesně ví o tom, jak se má updatovat state, ačkoli by mu to mělo být putna. Zaměříme se tedy na refaktor téhle části kódu:
378 | ```js
379 | const _handleTick = (time) => {
380 | updateState({
381 | time,
382 | })
383 | }
384 |
385 | const _handleFormChange = (nextValue) => {
386 | updateState({
387 | formValue: nextValue,
388 | })
389 | }
390 |
391 | const _handleFormSubmit = () => {
392 | updateState({
393 | todos: [{
394 | 'id': Date.now(),
395 | text: state.formValue,
396 | }].concat(state.todos),
397 | formValue: '',
398 | })
399 | }
400 | ```
401 | ### Změna Je život
402 | No tak si představme, že tyhle callbacky v aplikace vůbec nejsou, to by ta aplikace byla pouze jenom statická stránka, ne? Takový PHPečko. Vlastně tyhle funkce jsou to, co dávají téhle aplikaci smysl a jsou proto naprosto esenciální.
403 |
404 | První úkol samozřejmě bude je dostat pryč z komponety tj. pryč z View.
405 |
406 | Jak na to? Hmm...
407 |
408 | Ideální by bylo, abychom dokázali volat z callbacků jenom funkce, která by nějak sami od sebe dokázali aktualizovat stav.
409 |
410 | To znamená, že bychom dostali funkce jako prop a tu bychom zavolali na nějaký callback a bylo by to. Ta funkce by měla mít také přístup ke stavu, neboť například aktualizace `todos` je možná pouze na základě původního listu.
411 |
412 | Bylo by tedy super prostě jenom aktualizovat stav například u hodin:
413 | ```js
414 | export default ({ time, setTime }) => (
415 |
416 | )
417 | ```
418 | A funkce `setTime` by jenom vrace aktualizaci stavu
419 | ```js
420 | const setTime = (nextTime) => {
421 | return {
422 | time: nextTime,
423 | }
424 | }
425 | ```
426 | Hmmm, ale jak toho docílit? No... To je lehounce komplikované, budeme tuto funkci muset prohnat přes náš dekorátor a konteinerovou komponetu držící stav, takže upravíme dekorátor:
427 | ```js
428 |
429 | const createApp = (AppComponent) => (state, actions) => {
430 | return class extends React.PureComponent {
431 | state = state
432 |
433 | _updatState = (stateUpdate) => {
434 | this.setState(stateUpdate)
435 | }
436 |
437 | _createActions() {
438 | return Object.keys(actions).reduce((actualActions, actionName) => {
439 | actualActions[actionName] = (...args) => {
440 | return this._updatState(actions[actionName](...args, this.state))
441 | }
442 |
443 | return actualActions
444 | }, {})
445 | }
446 |
447 | render() {
448 | const props = {
449 | ...this.state,
450 | ...this._createActions(),
451 | }
452 |
453 | return
454 | }
455 | }
456 | }
457 | ```
458 | Metoda `_createActions` obalí funkce, které jí předáme tj: `createApp(Component)({ ..state }, { setTime })` -> funkci `setTime` takovou funkcí, která pokud je zavolaná tak pouze předá argumenty funkci `setTime` a výsledek její práce použije na aktualizaci stavu. K tomu ještě do funkce `setTime` přidá jako poslední argument aktuální stav, aby bylo vše jasné.
459 |
460 | Voila!
461 |
462 | ## Čeho jsme docílili?
463 | Pěkně jsme si zcela oddělili modelovou vrstvu a view vrstu a znatelně jsme rozsekali Controller. Z toho zbyla jenom čistá funkce, která pouze jenom předává data tam a callbacky je vrací zpátky.
464 |
465 | Tohle je opravdu elegantní řešení a dá se povýšit ještě o úroveň výše no a to si povíme příště...
466 |
467 | ## Co bude příště
468 | Nyní máme stav celé komponenty "přichycený" pouze na hlavní první root komponentě. To není tak špatné, ale jak aplikace roste a přidáme například taby nebo stránky, tak zjistíme, že by se hodilo mít tento stav přichycený k jiným komponentám, například k jednotlivým stránkám.
469 |
470 | No a to si ukážeme příště, jak "koukat" na stav z více míst v aplikaci pomocí neviditelného `contextu`.
471 |
--------------------------------------------------------------------------------
/pt.4-React-sprava-stavu/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pt.4",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "create-react-app": "^1.3.1",
7 | "false": "0.0.4",
8 | "global": "^4.3.2",
9 | "moment": "^2.18.1",
10 | "react": "^15.5.4",
11 | "react-dom": "^15.5.4"
12 | },
13 | "devDependencies": {
14 | "react-scripts": "0.9.5"
15 | },
16 | "scripts": {
17 | "start": "react-scripts start",
18 | "build": "react-scripts build",
19 | "test": "react-scripts test --env=jsdom",
20 | "eject": "react-scripts eject"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/pt.4-React-sprava-stavu/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webdev-js-evenings/react-workshop/31a0a48f1a5419003195abbb8dc19f90e88f4a62/pt.4-React-sprava-stavu/public/favicon.ico
--------------------------------------------------------------------------------
/pt.4-React-sprava-stavu/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
16 | React App
17 |
18 |
19 |
20 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/pt.4-React-sprava-stavu/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | animation: App-logo-spin infinite 20s linear;
7 | height: 80px;
8 | }
9 |
10 | .App-header {
11 | background-color: #222;
12 | height: 150px;
13 | padding: 20px;
14 | color: white;
15 | }
16 |
17 | .App-intro {
18 | font-size: large;
19 | }
20 |
21 | .list {
22 | width: 500px;
23 | margin: 1em auto;
24 | text-align: left;
25 | }
26 |
27 | @keyframes App-logo-spin {
28 | from { transform: rotate(0deg); }
29 | to { transform: rotate(360deg); }
30 | }
31 |
--------------------------------------------------------------------------------
/pt.4-React-sprava-stavu/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | });
9 |
--------------------------------------------------------------------------------
/pt.4-React-sprava-stavu/src/clock.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import moment from 'moment'
4 |
5 |
6 | export default class Clock extends React.Component {
7 | _timeout = null
8 |
9 | static defaultProps = {
10 | tickInterval: 'second',
11 | }
12 |
13 | componentDidMount() {
14 | this._timeout = setTimeout(this._handleTick, this.props.tick)
15 | }
16 |
17 | componentWillUnmount() {
18 | clearTimeout(this._timeout)
19 | }
20 |
21 | _handleTick = () => {
22 | this.props.onTick(
23 | moment(this.props.time)
24 | .add(this.props.tick / 1000, this.props.tickInterval)
25 | )
26 | setTimeout(this._handleTick, this.props.tick)
27 | }
28 |
29 | render() {
30 | return (
31 | {moment(this.props.time).format('H:mm:ss')}
32 | )
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/pt.4-React-sprava-stavu/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/pt.4-React-sprava-stavu/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import App from './just-component/just-component'
4 | import './index.css'
5 |
6 | ReactDOM.render(
7 | ,
8 | document.getElementById('root')
9 | );
10 |
--------------------------------------------------------------------------------
/pt.4-React-sprava-stavu/src/initial-state.js:
--------------------------------------------------------------------------------
1 | export default {
2 | todos: [
3 | { id: 1, text: 'Nákup pro maminku na SváTek maTek' },
4 | { id: 2, text: 'Nakoupit na víkendovou kalbu 2 kila lososa rozpejkací bagetky' },
5 | ],
6 | user: {
7 | name: 'Vojta',
8 | email: 'vojta.tranta@gmail.com',
9 | id: 666,
10 | },
11 | time: Date.now(),
12 | formValue: '',
13 | }
14 |
--------------------------------------------------------------------------------
/pt.4-React-sprava-stavu/src/just-component/just-component.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import logo from '../logo.svg';
3 | import '../App.css';
4 |
5 | import Clock from '../clock'
6 | import Form from '../todo-form/form'
7 | import Todo from '../todo-form/todo'
8 |
9 | import initialState from '../initial-state'
10 |
11 |
12 |
13 | const createApp = (AppComponent) => (state, actions) => {
14 | return class extends React.PureComponent {
15 | state = state
16 |
17 | _updatState = (stateUpdate) => {
18 | this.setState(stateUpdate)
19 | }
20 |
21 | _createActions() {
22 | return Object.keys(actions).reduce((actualActions, actionName) => {
23 | actualActions[actionName] = (...args) => {
24 | return this._updatState(actions[actionName](...args, this.state))
25 | }
26 |
27 | return actualActions
28 | }, {})
29 | }
30 |
31 | render() {
32 | const props = {
33 | ...this.state,
34 | ...this._createActions(),
35 | }
36 |
37 | return
38 | }
39 | }
40 | }
41 |
42 |
43 | const StateLessComponent = ({ ...state, addTodo, setTime, setTodoDraft }) => {
44 | return (
45 |
8 | )
9 |
--------------------------------------------------------------------------------
/pt.5-React-redux/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "react-app",
3 | "rules": {
4 | "semi": 0
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/pt.5-React-redux/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/pt.5-React-redux/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Naučte se React.js 6 - Redux
4 | ## Minule
5 | [Minulý workshop](../pt.4-React-sprava-stavu) ukázal, jak si vytáhnout stav z komponenty "někam jinam". Neboť už Sumerové, jak známo, zaznamenávali stavy jejich aplikací mimo view vrstvu. Nebyli to prasata, aby psali SQL query a připojení do databází přímo do "HTML".
6 |
7 | Vytažení stavu z "živých" komponent přineslo výhody v tom, že s kód komponent zpřehlednil. Nebylo už třeba řešit `setState()`, nebylo už ani potřeba rozlišovat mezi `this.state` a `this.props`. Najednou se mohly všechny komponenty přepsat do pouhých funkcí.
8 |
9 | ## Dneska
10 | Tak to je všechno pěkný, ale má to pár much. Zdůrazňoval jsem, že jádro každé aplikace - její byznysová logika - leží ve změnách. Tedy v tom, jak data v průběhu času transformujeme.
11 |
12 | Hezky navržená relační databáze je sama o sobě k ničemu, pokud se do ní nedá zapisovat a údaje měnit a nebo je vytahovat a prezentovat. Tohle všechno je **transformace dat**. Tedy merit každá aplikace.
13 |
14 | Dnešní workshop se tedy bude točit okolo transformací dat. Hlavně bude o tom, jak se začít dívat na aplikace spíš jako na sekvence událostí než pouhé "obrazovky".
15 |
16 | Jdeme na to.
17 |
18 | ## Apka
19 | Dneska se teda budeme bavit vytvářením apky, která vám pomůže spočítat daně. Hurá, konečně něco opravdu užitečného, což? Zapneme si tam dokonce Firbasku, aby to bylo trošku srandovnější - ale uvidíme, jestli se k tomu vůbec dostaneme. V první řadě si navrhneme tvar stavu aplikace.
20 |
21 |
22 | ## Model
23 | Tedy modelová vrstvička aplikace.
24 |
25 | K výpočtu daně potřebujeme znát určitě příjem. Příjem se dá vypočítat ze sumy pohledávek neboli vydaných faktur. Takže určitě bude potřeba seznam faktur.
26 |
27 | Každá faktura bude mít číslo, odběratele, cenu, DPH a celkovou cenu. Klasika.
28 |
29 | Faktury by mělo být možné přidávat upravovat atd atd. To je jasné.
30 |
31 | Daňová kalkulačka si pak vždycky při změně faktur přepočítá všechny potřebné informace. To je samozřejmě pouze jenom příjem, který je potřeba pro výpočet daně. Ještě si k tomu vypočítáme DPHáčko, podle toho, jak zbyde čas.
32 |
33 | Takže stav bude vypadat nějak takhle:
34 | ```js
35 | const initialData = {
36 | vatRatio: 21,
37 | invoices: [{
38 | 'id': '2017/1',
39 | 'customer': 'Avocode Inc.',
40 | 'price': 123000,
41 | 'VAT': 25830,
42 | 'total': 148830,
43 | }],
44 | tax: {
45 | income: 123000,
46 | costsRatio: 60,
47 | taxRatio: 15,
48 | tax: 7380,
49 | },
50 | user: {
51 | username: '',
52 | email: '',
53 | id: 0,
54 | }
55 | }
56 | ```
57 |
58 | Přihlášené user si tu zatím ponecháme a použijeme ho pro práci s Firebaskou.
59 |
60 | ## Akce!
61 | Tak ještě naposledy. To co utváří aplikace jsou **změny stavu**. To je po tvaru stavu to nejdůležitější v aplikaci. Proto je dobré o takových akcí mít vždy co největší podvědomí a vědět přesně, co dělají. Nebo dokonce co celá aplikace může dělat.
62 |
63 | V minulé lekci jsme neměli definováno nic jako "akce". Změnu stavu aplikace byla provedena buďto přes `this.setState()` na React komponentně.
64 |
65 | Nebo přes nějaký předaný callback přes props, který jsme si nějak sémanticky pojmenovali. Třeba `setTodoDraft()` nebo `addTodo()`.
66 |
67 | Tohle můžeme prohlásit za dostačující. Za docela hezkou architekturu.
68 |
69 | Jen drobné vylepšení můžeme poskytnout do naší aplikace tak, že přidáme nějakou jasnější informaci o tom, co se v aplikaci děje. Sice z názvu funkce `addTodo()` je jasné, že přidává další TODOčko, ale možná by nebylo od věci si akce pojmenovat nějakuo hezkou konstantou ve stringu třeba `ADD_TODO`.
70 |
71 | Tohle se může hodit například při debugování nebo při streamování akcí přes sockety do jiného počítače. Také můžeme pak poměrně jasně vidět, jaké akce jaká část aplikace využívá a můžeme si je sémanticky shlukovat. navíc nám to nedá tolik práce. Případné API si představuji takto:
72 | ```js
73 | const addTodo = action('ADD_TODO', (todoText) => {
74 | return {
75 | text: todoText,
76 | id: uuid.v4(),
77 | }
78 | }) => {
79 | action: 'ADD_TODO',
80 | payload: {
81 | text: todoText,
82 | id: id,
83 | }
84 | }
85 | ```
86 | Prostě jenom funkci obalíme.
87 |
88 | Další prvek vylepšení funkcí by mohlo být oddělení toho, jak se vytváří akce (jestli přichází jen z UI nebo jestli přichází jako nějaká aktivita třeba ze sítě nebo z URL apod.). Pokud tohle chování oddělíme můžeme si pak všimnou toho, že se naše aplikace izoluje od okolního světa:
89 |
90 | ```
91 | _________AKCE________
92 | | |
93 | A A
94 | --Kliknutí---> K K <----------HTTP--------
95 | --WebSocket--> C Naše apka C <-----history.push()---
96 | E E
97 | | |
98 | ---------AKCE---------
99 | ```
100 | To sem hezky nakreslil!
101 |
102 | Akce se chovají jako hranice mezi okolním světem (prohlížeč, klávesnice, síť) a naší aplikací, bude to taková hradba, aby se do apky nedostával bordel zvenčí.
103 |
104 | Jediná věc, které bude rozumět naše aplikace budou jenom akcičky. To znamená, že bude vědět, jak zpracovat takovýto příkaz:
105 | ```js
106 | {
107 | action: 'ADD_TODO',
108 | payload: {
109 | 'todoText': 'Nová todo',
110 | 'id': '12323SDFSDFadf',
111 | }
112 | }
113 | ```
114 | Ale nebude cesta, jak v apce něco "zavolat" nějakou metodu zvenčí. Jakákoliv komunikace s naší aplikací se bude muset přeložit do předem definované akce, které bude apka rozumět.
115 |
116 | Teď ale je otázka, jak bude apka akce zpracovávat. O to se postará Store.
117 |
118 | ## Store
119 | V předešlé lekci jako Store, neboli jako komponenta držící stav, nám postačila jen kmponenta. A posloužila opravdu dobře. Komponenty totiž sami od sebe umí vše překreslit při nějakém volání `setState()` takže nebylo potřeba vytváře nějaké listenery apod.
120 |
121 | Ale to, že stav byl je v komponentně sebou neslo tu nevýhodu, že byl pouze jenom celý přístupný jen v jedné "vrchní" chytré komponentě a né někde dole.
122 |
123 | Pokud si představíme nějaký strom komponent:
124 | 
125 |
126 | Tak všude vidíme tenhle nevinný obrázek. No jo, ale většina aplikací je daleko, daleko komplexnějších a mají v sobě ohromné zanoření komponentového stromu. Pokud máte jenom dvě úrovně, tak máte miniaplikačku. Takové Avocode má takových úrovní fakt mraky a každá velká aplikaci na tom bude podobně.
127 |
128 | To znamená, že by bylo dost šílené si předávat všechny potřebané props pouze z jednoho místa až dolů někam totálně hluboko do stromu. Stalo by se tak, že by komponenty dostávaly props, které by jenom přeposílaly dál a to je znak trošku smrdutého kódu neboť není potřeba, aby lord prosil Jeana, aby mu přinesl klavír, protože na něm má doutník. Každý by měl dostat jen to, co potřebuje a tak je to i s Props.
129 |
130 | Takže jak se toho zbavit.
131 |
132 | No, naštěstí tu máme velkou obeličku a tou je React a jeho `context`.
133 |
134 | ### Context
135 | Pokud jste o Contextu v Reactu neslyšeli, tak to je takový maličký hack. Je to vlastně takový DI container zabudovaný v Reactu, který funguje pouze přes Stringy. Jeho použití je následující.
136 |
137 | Řekněme, že máme službu třeba `API`, které requestuje nějaké endpointy. Pokud byste se striktně držely Reactí filozofie, tak byste si museli předávat referenci na `API#get()` do stromu komponent až tam, kde je to potřeba. To je ale trošku šílené, ne?
138 |
139 | Proč takle radši APIčko Reactu nepředhodit, aby ho distribuoval, kde je potřeba:
140 | ```js
141 | class extends React.Component {
142 | static childContextTypes = {
143 | api: React.PropTypes.Object,
144 | }
145 |
146 | getChildContext() {
147 | return {
148 | api: new Api(new HttpRequestFactory()),
149 | }
150 | }
151 |
152 | render() {
153 | return (
154 | // ta má dítě DiteHlavniKomponenty
155 | )
156 | }
157 | }
158 |
159 | class DiteHlavniKomponenty extends React.Component {
160 | static contextTypes = {
161 | api: React.PropTypest.object.isRequired,
162 | // řekneme si o api v context, něco jako v /* @inject */ v Nette
163 | }
164 |
165 | render() {
166 | const api = this.context.api // Juhů takle je API dostupné a nic není potřeba předávat přes props.
167 |
168 | return (..)
169 | }
170 | }
171 | ```
172 | Parádička. Samozřejmě zkušení architekti cítí, že je to prasárnička, ale to přejdeme. My si totiž ten kontext pečlivě schováme, abychom se o něj nemusely starat!
173 |
174 | ### Context provider
175 | Často můžete vidět kód:
176 | ```js
177 | render() {
178 | return (
179 |
180 |
181 |
182 | )
183 | }
184 | ```
185 | Takle se hází do Reactího stromu komponent kontext. Takováhle `ApiContextProvider` komponenta pak uvnitř vypadá takto:
186 | ```js
187 | class ApiContextProvider extends React.PureComponet {
188 | static contextTypes = {
189 | api: React.PropTypes.object,
190 | }
191 |
192 | getChildContext() {
193 | return {
194 | api: this.props.api,
195 | }
196 | }
197 |
198 | render() {
199 | return this.props.children
200 | }
201 | }
202 | ```
203 | Takováhle komponenta pouze vezme svoje props a hodí je do contextu, aby byly přístupny hluboko v DOMu bez nutnosti je předávat.
204 |
205 | Této vlastnosti využijeme, abychom už ale naprosto oddělili stav aplikace od Reactu a jeho API.
206 |
207 | #### Connect
208 | Connect bude dekorátor, kterým obalíme komponentu proto, aby byla schopná přijmout data z jednoho hlavní storu kdekoliv ve stromu komponent. Tedy ne už někde na vrcholu stromu, ale klidně někde hluboko ve stromu. API si předtavuji takto:
209 | ```js
210 | class ConnectedReactComponent extends React.Component {
211 | render() {
212 | console.log(this.props.invoicesFromTheStore)
213 |
214 | return (...)
215 | }
216 | }
217 |
218 | connect({
219 | 'invoices': 'invoicesFromTheStore', // klíč - klíč ve stavu, hodnota - název klíče v props, který půjde do componenty
220 | }, actions)(ConnectedReactComponent)
221 | ```
222 | Voila, elegantní, že? Samozřejmě komponenta se bude chovat porádně stejně jako každá jiná, jen bude trošku obohacena o pár klíčů ze Store.
223 |
224 | Druhý parametry funkce `connect()` bude objekt s akcemi, ty pak přijdou do komponenty jako props:
225 | ```js
226 | connnect(propsFromStore, {
227 | 'onInvoiceAdd': () => ({ // V komponentě pak bude tahle funkce přístupná jako props.onInvoiceAdd
228 | 'invoice': { // výseledek volání funkce bude přímu dispatchnut přes store
229 | price: 300,
230 | VAT: 21,
231 | }
232 | })
233 | })(ConnectReactComponent)
234 | ```
235 |
236 |
237 | ### Gimme da Store
238 | Takže store nebude nic jiného než event emitter, který bude mít referenci na stav a bude ho průběžně měnit:
239 | ```js
240 | class Store {
241 | _listeners = []
242 | _state = {}
243 |
244 | constructor(initialState) {
245 | this._state = initialState
246 | }
247 |
248 | _emitChange() {
249 | this._listeners.forEach(listener => listener())
250 | }
251 |
252 | listen(listener) {
253 | this._listeners.push(listener)
254 | }
255 |
256 | getState() {
257 | return this._state
258 | }
259 | }
260 | ```
261 | Ok, super, jak ale budeme ten stav měnit?
262 |
263 | Na si vytvoříme metodu `dispatch()`, která převezme akci a aplikuje ji na stav... Hmmmm. Aplikuje akci na stav:
264 | ```js
265 | aplikujAkciNaStav(stav, akce) => nový stav
266 | ```
267 | Tohle je hrozně jednoduchá myšlenka, na které se dá stavět.
268 |
269 | Přece naše aplikace jest pouze a jenom o změnách nějakého stavu. Stav máme nadefinovaný a změna stavu - no minimálně její jedna část je akce, pamatujete? To je prostě jendoduchý popis změny:
270 | ```js
271 | {
272 | action: 'ADD_TODO',
273 | payload: {
274 | 'todoText': 'Nová todo',
275 | 'id': '12323SDFSDFadf',
276 | }
277 | }
278 | ```
279 | Takováhle akce říká: Moje drahá aplikace, proveď prosímtě změnu stavu, kterou jsme pojmenoval `ADD_TODO` tak, že přidáš nové todočka s textem `Nová todo` a idčkem `12323SDFSDFadf`. Postarej se o to.
280 |
281 | Dobře, takže máme vydefinovanou akci, teď ale jak aplikovat změnu. Potřebujeme totiž změni stav aplikace za pomoci výše vydefinované akce. Jak na to?
282 |
283 | Takže my chceme vzít jednu akci a jeden stav a vytvořit z ní jednu věc -> nový stav.
284 |
285 | To je vlastně redukování, vezmeme stav a akci a vrátíme změněný stav (nový stav). To je přece jasně zapsané tady:
286 | ```js
287 | aplikujAkciNaStav(stav, akce) => nový stav
288 | ```
289 | Takže stejně tak naimplementujeme metodu dispatch...
290 | ```js
291 | dispatch({ action, payload }) {
292 | if (action === 'ADD_TODO') {
293 | this._state.todos.push({
294 | text: payload.todoText,
295 | id: payload.id
296 | })
297 |
298 | this._emitChange()
299 | }
300 | }
301 | ```
302 |
303 | A je to... No.. Je to. Trošku tu lžu, tohle nevrací totiž nic. Co ale kdybychom si tu změnu předali přes konstruktor do Storu?
304 |
305 | A jak se ta změna jmenuje? No když něco `redukujeme`, tak se jedné o `reducer`:
306 | ```js
307 | class Store {
308 | _listeners = []
309 | _state = {}
310 | _reducer = null
311 |
312 | constructor(initialState, reducer) {
313 | this._state = initialState
314 | this._reducer = reducer
315 | }
316 |
317 | //...
318 | }
319 | ```
320 | A teď metoda dispatch lépe a hezčeji:
321 | ```js
322 | dispatch(action) {
323 | this._state = this._reducer(this._state, action)
324 | this._emitChange()
325 | }
326 |
327 | // samotný reducer - musí být čistá funkce
328 | const reducer = (state, { action, payload }) => {
329 | if (action === 'ADD_TODO') {
330 | state.todos.push({
331 | text: payload.todoText,
332 | id: payload.id
333 | })
334 | }
335 |
336 | return state
337 | }
338 |
339 | // předání reduceru
340 | new Store(initialState, reducer)
341 | // Tadáá
342 | ```
343 | A to je všechno! Máme naprosto luxusní nový store, který umí naprosto všechno, co naše aplikace bude pro změnu stavu potřebovat. Fakt!
344 |
345 | Teď si to dáme hezky všechno dohromady, to znamená, že si vytvoříme:
346 | - `StoreContextProvider`
347 | - `Store`
348 | - `Actions creators`
349 | - `connect()` funkce pro pro spojování komonent se storem kdekoliv ve stromu
350 |
351 | Tak si napimplementujeme přidávání Faktur. Mělo by to fungovat tak, že klikneme na tlačítku `Přidat fakturu`, vyskočí modální okno a tam bude jednoduchý formulář pro přidání faktury.
352 |
353 | Do toho! Uvidíme co stihneme, příště to napojíme na Firebasku a live updaty!
354 |
--------------------------------------------------------------------------------
/pt.5-React-redux/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pt.5-react-redux",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "avocode-ui": "^7.1.2",
7 | "false": "0.0.4",
8 | "react": "^15.5.4",
9 | "react-dom": "^15.5.4"
10 | },
11 | "devDependencies": {
12 | "react-scripts": "1.0.7"
13 | },
14 | "scripts": {
15 | "start": "react-scripts start",
16 | "build": "react-scripts build",
17 | "test": "react-scripts test --env=jsdom",
18 | "eject": "react-scripts eject"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/pt.5-React-redux/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webdev-js-evenings/react-workshop/31a0a48f1a5419003195abbb8dc19f90e88f4a62/pt.5-React-redux/public/favicon.ico
--------------------------------------------------------------------------------
/pt.5-React-redux/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 | React App
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/pt.5-React-redux/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/pt.5-React-redux/src/App.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: Verdana, Helvetica, sans-serif;
3 | }
4 |
5 | .App {
6 | text-align: center;
7 | }
8 |
9 | .App-logo {
10 | animation: App-logo-spin infinite 20s linear;
11 | height: 80px;
12 | }
13 |
14 | .App-header {
15 | background-color: #222;
16 | height: 150px;
17 | padding: 20px;
18 | color: white;
19 | }
20 |
21 | .App-intro {
22 | font-size: large;
23 | }
24 |
25 | .column {
26 | display: flex;
27 | flex-direction: column;
28 | }
29 |
30 | .row {
31 | display: flex;
32 | flex-direction: row;
33 | }
34 |
35 | .flex-1 {
36 | flex: 1;
37 | }
38 |
39 | .flex-2 {
40 | flex: 2;
41 | }
42 |
43 | .flex-3 {
44 | flex: 3;
45 | }
46 |
47 | .flex-4 {
48 | flex: 4;
49 | }
50 |
51 | .flex {
52 | flex: 1;
53 | }
54 |
55 | .flexbox {
56 | display: flex;
57 | }
58 |
59 | table {
60 | table-layout: fixed;
61 | width: 100%;
62 | }
63 |
64 | td {
65 | text-align: left;
66 | }
67 |
68 | th {
69 | font-weight: normal;
70 | background: #e6e6e6;
71 | }
72 |
73 | .key {
74 | font-weight: bold;
75 | }
76 |
77 | .tax {
78 | padding: 2em 0;
79 | font-size: 1.5em;
80 | color: #D40455;
81 | }
82 |
83 | .modal-container {
84 | position: fixed;
85 | top: 0;
86 | left: 0;
87 | width: 100%;
88 | min-height: 100%;
89 | }
90 |
91 | .modal {
92 | background: white;
93 | width: 500px;
94 | text-align: left;
95 | top: 50%;
96 | left: 50%;
97 | transform: translate(-50%, -50%);
98 | position: absolute;
99 | overflow-y: scroll;
100 | }
101 |
102 | .centered {
103 | text-align: center;
104 | }
105 |
106 | .modal-content {
107 | padding: 1em 2em;
108 |
109 | }
110 |
111 | .modal-title {
112 | background: blue;
113 | color: white;
114 | font-weight: lighter;
115 | margin: 0;
116 | padding: .3em 0;
117 | font-size: 1.2em;
118 | }
119 |
120 | .modal-overlay {
121 | position: absolute;
122 | top: 0;
123 | left: 0;
124 | width: 100%;
125 | height: 100%;
126 | background: rgba(0, 0, 0, 0.5);
127 | }
128 |
129 | .form-control {
130 | font-size: 1em;
131 | padding: .3em .5em;
132 | }
133 |
134 | .form-group {
135 | margin: 1em 0;
136 | }
137 |
138 | .btn {
139 | background-color:#44c767;
140 | -moz-border-radius:3px;
141 | -webkit-border-radius:3px;
142 | border-radius:3px;
143 | border:1px solid #18ab29;
144 | display:inline-block;
145 | cursor:pointer;
146 | color:#ffffff;
147 | font-family:Arial;
148 | font-size:17px;
149 | padding: 8px 16px;
150 | text-decoration:none;
151 | text-shadow:0px 1px 0px #2f6627;
152 | }
153 | .btn:hover {
154 | background-color:#5cbf2a;
155 | }
156 | .btn:active {
157 | position:relative;
158 | top:1px;
159 | }
160 |
161 | .btn-danger {
162 | background-color:#c23a41;
163 | -moz-border-radius:3px;
164 | -webkit-border-radius:3px;
165 | border-radius:3px;
166 | border:1px solid #850808;
167 | display:inline-block;
168 | cursor:pointer;
169 | color:#ffffff;
170 | font-family:Arial;
171 | font-size:17px;
172 | padding: 8px 16px;
173 | text-decoration:none;
174 | text-shadow:0px 1px 0px #2f6627;
175 | }
176 | .btn-danger:hover {
177 | background-color:#B40455;
178 | }
179 | .btn-danger:active {
180 | position:relative;
181 | top:1px;
182 | }
183 |
184 | .form-buttons {
185 | text-align: center;
186 | padding: 1em 0;
187 | }
188 |
189 | .form-buttons .btn {
190 | margin: 0 .5em;
191 | }
192 |
193 | label {
194 | display: block;
195 | }
196 |
--------------------------------------------------------------------------------
/pt.5-React-redux/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './App.css';
3 |
4 | import InvoiceTable from './components/invoice-table'
5 | import TaxesCalculator from './components/taxes-calculator'
6 | import { connect } from './store'
7 |
8 |
9 | class App extends Component {
10 | render() {
11 | return (
12 |