Rádi v MoroSystems zkoušíme nové technologie a pokud jejich použití dává smysl, zavádíme je okamžitě do praxe. Nedávno jsme udělali menší revoluci a přešli z jQuery, se kterým máme rozsáhlé dlouholeté zkušenosti, na React, konkrétně ES6, Less, Redux, Webpack a npm.

Proč jsme začali o použití Reactu vůbec uvažovat? Přiměl nás k tomu projekt, na kterém několik let pracujeme – systém Sparc pro eBay. Klient nám zadal vývoj zcela nového a velmi rozsáhlého modulu, který je velmi náročný nejen na samotný vývoj a vývojový proces, ale také na výkon aplikace, její použitelnost z hlediska efektivity práce s rozhraním a dostupnost aplikace.

Tento nový seriál Tomových článků by měl nabízet tipy komukoli, kdo se pustí do psaní aplikace v podobných technologiích ve více než jednom či dvou lidech nebo třeba i těm, kteří o použití těchto technologií zatím jen uvažují.

První díl přináší pár základních technologických a designových poznatků, které se nám osvědčili a mohou se hodit úplně každému.

Unit Testy

Zprovoznění rychlých unit testů na npm run test:watch bylo jednou z nejlepších věcí, co mohla lidi, zvyklé na špagetový jQuery kód, potkat. Umožnila použití TDD, ke kterému zatím přistupujeme poměrně konzervativně, ale oproti stavu kdy 99% UI kódu bylo netestovatelné, je to velký pokrok.

testing-darth-vader

Inspirovali jsme se přístupem A STEP-BY-STEP TDD APPROACH ON TESTING REACT COMPONENTS USING ENZYME. V našem případě jsou testovatelné nejen React komponenty, ale i akce a reducery.


import React from "react";
import {expect} from "chai";
import {shallow} from "enzyme";
import YesNoFormatter from "./YesNoFormatter";
import FontAwesome from "react-fontawesome";

describe('<yesnoformatter></yesnoformatter>', () => {
it('should exists', () => {
expect(YesNoFormatter).to.not.equal(null);
});

it("should render FontAwesome element with class 'formatter-yesNo'", () => {
const elem = shallow(<yesnoformatter></yesnoformatter>);
const value = elem.find(FontAwesome);
expect(value.hasClass("formatter-yesNo")).to.be.equal(true);
});

it("should have default false value prop", () => {
const elem = shallow(<yesnoformatter></yesnoformatter>).find(FontAwesome);
const elem1 = shallow(<yesnoformatter value={false}></yesnoformatter>).find(FontAwesome);
expect(elem.prop("name")).to.be.equal(elem1.prop("name"));
});

it("should render 'check' FontAwesome when value is true", () => {
const elem = shallow(<yesnoformatter value={true}></yesnoformatter>);
const value = elem.find(FontAwesome);
expect(value.prop("name")).to.be.equal("check");
});

it("should render 'times' FontAwesome when value is false", () => {
const elem = shallow(<yesnoformatter value={false}></yesnoformatter>);
const value = elem.find(FontAwesome);
expect(value.prop("name")).to.be.equal("times");
});
});

Výpis testu pak vypadá takto:


<yesnoformatter></yesnoformatter>
√ should exists
√ should render FontAwesome element with class 'formatter-yesNo'
√ should have default false value prop
√ should render 'check' FontAwesome when value is true
√ should render 'times' FontAwesome when value is false

Less.css

Použití nějakého CSS preprocesoru, v naše případě Less, lze ale použít i Sass, je už v dnešní době na větších projektech standardem. Skvělým příkladem je zanořování pravidel. Dva hlavní důvody, pro které Less používáme, jsou kontrola nad CSS namespace, a proměnné (především barvy).

Kontrola namespace

V každém větším projektu časem přijde CSS do stavu, kdy v tisíc-řádkovém souboru není možné reálně nic najít, a tím pádem neustále dál roste a roste. Pro tento účel jsme kód strukturovali tak, aby existovalo jen několik málo míst, kde se definuje, na jakou konkrétní třídu se má něco použít od toho, jak to má vypadat.

Uvedu konkrétní příklad: řekněme že máme soubor NoneFormatter.less, ve kterém je obsažena definice toho, jak má daná komponenta vypadat.


.none-formatter(){
color: gray;
font-weight: normal;
font-size: 12px;
}

To samotné ale nestačí k tomu, aby se to někde projevilo. K tomu potřebujeme tento mixin navázat na nějaký selektor. Od toho je zde pak soubor components/index.less, kde jsou všechny tyto definice reálně použity.


@import (reference) "NoneFormatter";
@import (reference) "UserFormatter";
@import (reference) "UserListFormatter";
@import (reference) "YesNoFormatter";

.userlist {
.userlist();
}

.formatter-none {
.none-formatter();
}

.formatter-user {
.user-formatter();
}

.formatter-yesNo {
.YesNoFormatter();
}

Důležité je, že v případě potřeby zjistit, kde je nějaký styl použit nebo jaké všechny třídy jsou obsazeny, stačí pohled do jednoho souboru (nebo do několika málo souborů). Za povšimnutí také stojí použití volby reference pro import, která zajišťuje, že z includovaných souborů se nic nedostane do globálního namespace. Includují se pouze mixiny které je nutno ručně použít

Další přidanou hodnotou je to, že pokud se někdy rozhodneme vytvořit prvek, který vypadá jako userlist z příkladu výše, stačí na patřičný prvek aplikovat už hotový mixin a máme prakticky hotovo.

Proměnné

Různé úpravy barev, velikostí a odsazení jsou při ladění aplikace na denním pořádku a je tedy ideální mít je na jednom místě. Proto používáme a rozšiřujeme přístup od Bootstrapu, kdy klíčové barvy, velikosti a podobně nedáváme do stylů napevno, ale referencujeme je přes proměnné.


@import "../../main/less/colors";

.none-formatter(){
color: lighten(@gray-light, 25%);
font-weight: normal;
font-size: @font-size-base;
}


//***************DEFAULTS
@import "~bootstrap/less/variables";

//***************OVERRIDED
@border-radius-base: 2px;
@border-radius-large: @border-radius-base;
@border-radius-small: 2px;

@input-border: #ddd;
@text-color: @gray;
@headings-color: @gray-dark;
@brand-info: #FCFCFC;
@brand-primary: #296CA3;

@state-info-text: #31708f;
@state-info-bg: lighten(#d9edf7, 7%);

@font-size-large: 18px;

//***************CUSTOM
//Backgrounds
@sparc-dark-panel-background: #ddd;
@sparc-basic-panel-background: #FFFFFF;
@sparc-light-panel-background: #F5F5F5;

Tímto přístupem jsme schopni změnit primární barvu aplikace (call to action prvky, aktivní barvy prvků, různá podbarvení, odkazy, …), @brand-primary, během několika sekund.

To vše ale jedině za předpokladu, že komponenty jsou napsány tak, aby s tímto přístupem spolupracovaly. To vyžaduje poměrně striktní code review pro lidi, kteří nejsou zvyklí CSS styly příliš řešit.

PageLayout komponenty

Jak často slyšíte požadavek typu: “Chtěl bych novou stránku, aby vypadala stejně jako ten dashboard co tam máme…”.

Většinou to znamená zkopírovat html stránky, nějaké ty css styly, napojit to na jiná data atd… Jenže ve skutečnosti chce programátor napsat asi toto: “Použij standardní layout pro Dashboard, do záhlaví dej tenhle text, vlevo dej komponentu pro filtr, doprostřed tabulku a do pravého panelu nic dávat nebudeme”.

Čemuž odpovídá zhruba tento kus kódu, který se postará o všechny styly, formátování atd. Co si na patřičná místo vloží programátor, to už je jeho věc.


import {DashboardLayout} from "../../../layout";
import TableContainer from "./containers/TableContainer";
import FilterContainer from './containers/FilterContainer';

const Component = () => {
const layoutParams = {
topSection: <h1>My static header</h1>,
leftSection: <filtercontainer></filtercontainer>,
mainContentSection: <tablecontainer></tablecontainer>,
rightSection: null //for demonstration, not needed to specify
};
return (<dashboardlayout {...layoutParams}></dashboardlayout>);
};

Nejedná se o žádnou převratnou novinku, je to standardní přístup z komponentových frameworků (Spring, Vaadin, …). Byl ale v průběhu vývoje webových technologií trochu pozapomenut.

Formatter komponenty

Dalším typem komponenty, kterou určitě chcete mít v aplikaci je něco, co nazýváme Formatter. Stejně jako v aplikaci existuje standardní cesta, jakou zadávat např datumy nebo text, měl by existovat i způsob, jakým standardně zobrazovat (formátovat) určitý typ informace (string, datum, uživatel, boolean). Tyto formattery jsou pak použity všude, kde daný typ informace zobrazujeme, ať už natvrdo v kódu nebo dynamicky dle nějakého informace z backendu (sloupeček v tabulce).

Kromě očividných výhod typu, že změna zobrazení se provádí na jednom místě (DRY), to vede i k dobrého UX patternu – jednotný styl prezentace informací tlačen zespoda směrem od týmu (“Vážně chceš tady pro to místo naprogramovat speciální formátování? To zabere dost času. Kdybychom ale použili to co máme všude jinde…“).

Reálný příklad z dnešního dne z projektu je změna YesNo formatteru, kdy během 5 minut celá aplikace na všech místech prokoukla tím že se přidala ikonka místo textu.


//original
const YesNoFormatter = ({value}) => (
<span className="formatter-yesNo">{value ? "Yes" : "No"}</span>
);

//new
const YesNoFormatter = ({value}) => (
<fontawesome className="formatter-yesNo" name={value ? "check" : "times"}></fontawesome>
);

Na co se můžete těšit v dalším díle

Když jsem se ptal Toma na to, o čem bude psát v dalším díle, abych to zde uvedl jako teaser, odpověděl mi:

“To právě nevím, jinak bych to napsal už teď :-D. Určitě bude do dalšího dílů možnost psát o nějaké infrastruktuře. Případně můžem pak některé části kódu dát k dispozici, ale to jsem nechtěl slibovat/psát předčasně, než se bude vědět přesně co a jak. A určitě budem moct popisovat strukturu projektu, tu jsem necpal sem bo jsem si to ještě netroufal to popsat.”.

Tak si z toho vyberte, zůstaňte naladěni a těšte se na příště :-)


Tomáš Jílek

Tom_smallTomáš je fullstack Senior Developer se zaměřením na front-end a UX. Má rád JavaScript, nové technologie a pěkné, rychlé a dobře použitelné uživatelské rozhraní.

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.