Přeskočit na hlavní obsah

React a Typescript

V poslední době se mě několik lidí ptalo, jestli píši v čistém javascriptu, nebo používám něco jiného. Odpověď je jednoduchá: "Mým hlavním jazykem je Typescript". Důvody proč právě Typescript, jsem zmiňoval v jednom z předešlých článků.

Dnes bych se chtěl zaměřit na to, jak vlastně využít Typescript s Reactem. Dnešní článek bude více zaměřen na jednotlivé ukázky, než na teoretickou část.

Kde začít?


V první řadě je nutné říci, že díky tomu, že React je Facebook technologie, primárně své ukázky uvádí buď v čistém javascriptu, nebo pomocí Flow. Důvod, proč právě Flow je zřejmý, je to technologie, která je také od Facebooku.

V té chvíli nastává problém, kde přesně zjistit, jak používat Typescript. Nezbývá tedy nic jiného, než že projdete několik blogů a ukázek na githubu.

Abych vám ušetřil čas, který byste museli trávit při hledání "správného řešení", zkusím projít jednotlivé části, na kterých ukážu, jak Typescript v Reactu použít. V případě ukázky v čistém javascriptu, bude použita ES6 specifikace.

Pojďme tedy na to....

Functional Stateless Components


První, na co se zaměřím jsou stateless komponenty. Jinými slovy, jsou to komponenty, které jsou v podstatě funkcí a neobsahují ani state, ani možnost využítí lifecycle. Jediné, co máte k dispozici jsou props.

React Stateless componenty byly představeny ve verzi 0.14 a veřte, že by vaše aplikace měla být složena z 95%, právě těmito komponentami.

Javascript - Stateless component
import React from 'react';

const HelloWorld = ({name}) => (
    <div>{`My name is ${name}`}</div>
);

export default HelloWorld;

Typescript - Stateless component
import * as React from 'react';

interface Props {
    readonly name: string;
}

export const HelloWorld: React.SFC<Props> = ({name}) => (
    <div>{`My name is ${name}`}</div>
);

První, čeho si můžete všimnout je import. Tato drobná změna není až tak důležitá. Stačí se podívat, jakým způsobem se importuje v Typescriptu a bude vám to jasné.

Druhou věcí je interface. To už je zajímavější část. Důvod, proč definujeme interface jakožto datový typ pro props, je ten, abychom měli danou komponentu "typově pod kontrolou". V rámci definice tohoto typu si můžete všimnout i klíčového slova "readonly". Osobně toto klíčové slovo vnímám jako jednu z ohromných výhod Typescriptu a umožňuje mi nastavovat, že daný atribut je immutable. A pokud víte, jak pracuje props v React komponentách, tak je to přesně vlastnost, kterou chcete využít. Dá se říci, že téměř všechny atributy v typech označuji tímto klíčovým slovem. Immutable stav je přesně to, co v Reactu chceme mít.

Další věcí je export. Sice máte možnost i Typescriptu použít export default, ale nedělejte to. Důvod proč, je vcelku jednoduchý. V případě, že použiji export default, tak v souboru, kam importuji tuto komponentu nemám pod kontrolou její název. Poté se dost zesložiťuje nejen refactoring kódu, ale také i případné dohledávání jednotlivých vazeb. Zjednodušeně řečeno, export default není zrovna dobrá volba.

Typescript - import with default
import NazevKteryJsemSiVymyslel from './HelloWorld';

Typescript - import without default
import {HelloWorld} from './HelloWorld';

Poslední věcí je definování typu pro React Stateless komponentu. K tomuto účelu existuje právě zmíněný deklarovaný typ SFC. Jde o zkratku "StatelessComponent", která je v React typové definici také. Osobně spíše využívám zkratku SFC, která mi šetří místo, které mohu využít pro výčet atributů z props přes destructuring assignment.

Stateful components


Druhou variantou jsou stateful komponenty. Tyto komponenty je vhodné využívat v případě kontejnerů, tedy komponent, které jsou napojeny na Redux. Dalším případem, kdy má stateful komponenta využití, je, pokud potřebujeme state či lifecycle.

Javascript - Stateful component
import React from 'react';

class HelloWorld extends React.Component {

    render() {
        return (
            <div>Hello world!</div>
        );
    }
}

export default HelloWorld;

Typescript - Stateful component
import * as React from 'react';

interface Props {

}

export class HelloWorld extends React.Component<Props, void> {

    render() {
        return (
            <div>Hello world!</div>
        );
    }
}

V tomto jednoduchém případě se příliš věcí nemění. Kromě již zmíněného importu je možné si všimnout, že díky Typescriptu mohu využít generiku, kde prvním parametrem jsou props a druhým state. V případě, že state nepoužíváte (což v případě Reduxu je požadovaný stav), je možné state nastavit na void.

Pojďme se podívat trochu dál....

Redux


Pokud ve své aplikaci používáte Redux, musíte splnit několik kroků, abyste Redux mohli používat. Nechci se zde zabývat tím, jak nastavit Redux, ale jak v rámci Reduxu používat Typescript.

První věc, kterou byste měli udělat, je vytvoření typové definice vašeho state. S největší pravděpodobností budete svojí aplikaci rozdělovat do menších celků, které na konci spojíte pomocí combineReducers.

Nejdříve si tedy navrhneme Redux state, který bude reprezentován následujícími typy. Pro zjednodušení je vše napsáno v jedné části, nicméně je dobré, aby kazdý interface byl ve vlastním souboru.

Typescript - Redux state
export interface UsersState {
    readonly list: User[];
    readonly isFetching: boolean;
    readonly lastFetched: Date;
}

export interface TenantsState {
    readonly list: Tenant[];
    readonly isFetching: boolean;
    readonly lastFetched: Date;
}

export interface State {
    readonly users: UsersState;
    readonly tenants: UsersState;
}

Další věcí jsou redux akce, které budeme chtít z komponent volat, aby modifikovaly redux pomocí reducerů. Definice akcí by mohla vypadat následovně:

Typescript - Redux actions
import {Dispatch} from 'redux';
import {State} from './State';
import {fetch} from 'tva-preferovana-knihovna';

const PREFIX = 'USERS_';

export const UsersActions = {
    FETCHING: `${PREFIX}FETCHING`,
    FETCHED: `${PREFIX}FETCHED`,
    fetchUsers() {
        return (dispatch: Dispatch<State>, getState: () => State) => {
            dispatch({type: UsersActions.FETCHING});
            fetch('Zde bude GraphQL dotaz - REST je mrtvy :)').then((result) => {
                dispatch({type: UsersActions.FETCHED, payload: result});
            }).catch((err) => {
                // Zpracovani chyby, klidne pres dalsi Redux akci
            });
        };
    },
};

Nyní mám vytvořenu akci fetchUsers(), která nejdříve odešle do reduceru akci, že se data nahrávají a poté v Promise callbacku provede druhou akci, která má již i payload, kde jsou stažena daná data.

Další část skládanky jsou samozřejmě reducery. V ukázce používám knihovnu redux-actions, kterou považuji za vhodnou, pokud se chcete vyhnout psaní pomocí switch-case.

Typescript - Reducers
import {handleActions} from 'redux-actions';
import {UsersState as State} from './UsersState';
import {UsersActions as Actions} from './UserActions';

const initialState = {
    list: [],
    isFetching: false,
    lastFetched: null,
} as State;

export const UsersReducer = handleActions<State, any>({

    [Actions.FETCHING]: (state: State): State => {
        return {...state, isFetching: true};
    },

    [Actions.FETCHED]: (state: State, action: Action<User[]>): State => {
        return {...state, isFetching: false, list: action.payload, lastFetched: new Date()};
    },

}, initialState);

Redux máme hotový. Teď už zbývá pouze daný Redux napojit na container.

React Containers


Jak už jsme si řekli, kontejnery jsou v podstatě React Stateful komponenty, které jsou napojené na Redux. Pojdmě si takový kontejner zkusit napsat a využít typovosti, kterou nám nabízí Typescript.

Typescript - React container
import * as React from 'react';
import {bindActionCreators, Dispatch} from 'redux';
import {connect} from 'react-redux';
import {State} from './State';
import {User} from './User';
import {Button} from './Button';
import {UsersList} from './UsersList';

interface OwnProps {
}

interface ConnectedState {
    readonly list: User[];
}

interface ConnectedDispatch {
    readonly fetchUsers: () => void;
}

const mapStateToProps = (state: State): ConnectedState => ({
    list: state.users.list,
});

const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => {
    return bindActionCreators({
        fetchUsers: UsersActions.fetchUsers,
    }, dispatch);
};

class Container extends React.Component<ConnectedState & ConnectedDispatch & OwnProps, void> {

    componentWillMount(): void {
        const {fetchUsers} = this.props;
        fetchUsers();
    }

    handleOnClickRow = (user: User) => {
        console.log('Kliknul jsem na radek: ', user);
    };

    render() {
        const {list, fetchUsers} = this.props;
        return (
            <div>
                <Button onClick={fetchUsers}>Refresh</Button>
                <UsersList data={list} onClickRow={this.handleOnClickRow}/>
            </div>
        );
    }
}

export const UsersContainer = connect<ConnectedState, ConnectedDispatch, OwnProps>(mapStateToProps, mapDispatchToProps)(Container);

Uf, vypadá to šíleně, že? Ale pojďme si to rozebrat a vysvětlit, že to má svůj důvod :)

Co se týče importů, tak tam se nic zajímavého neděje, importujeme to, co v daném kontejneru budeme potřebovat.

Poté vytvoříme tři typy pro props.

První je OwnProps, který říká, že pokud bychom komponentu někdě importovali, je možné přes tento interface doplnit vlastní props. Například v případě, že je kontejner stránkou, na kterou se odkazujete přes React Router 4, bude interface vypadat následovně:

Typescript - Own props with React Router 4
import {RouteComponentProps} from 'react-router';

interface OwnProps extends RouteComponentProps<void> {
}

ConnectedState je typ, který definuje, co jsme z Reduxu vlastně získali. Tento interface je přímo spojen s funkcí mapStateToProps.

ConnectedDispatch je typ, který definuje Redux dispatch akce, které budeme v komponentě volat. Tento typ je přímo spojen s mapDispatchToProps.

V mapDispatchToProps si můžete všimnout funkce bindActionCreators, která přijímá dva parametry. Prvním je objekt obsahující dispatch funkce a v druhém je instance dispatch, která je nutná pro vykonání akce. Návratovou hodnotou je objekt obsahující jednotlivé akce, které jsou volány přes dispatch.

Samotná komponenta není až tak zajímavá. Zjednodušeně říká, že ve chvíli, kdy bude poprvé použita, automaticky načte data. Tyto data získáme z Reduxu do props a předáme je komponentě UsersList, která reprezentuje tabulku uživatelů.

Na konci si můžete všimnout, jakým způsobem se provede mapování na Redux. Metoda connect přijímá dva callbacky, což jsou mapStateToProps a mapDispatchToProps a vrací funkci, jejímž parametrem je naše komponenta, kterou chceme napojit.

V tomto případě tedy neexportujeme třídu, reprezentující naší komponentu, ale výsledek metody connect, který obsahuje naší komponentu, obohacenou o napojení na Redux. Zde také můžete vidět, proč existují tři typy pro props. Metoda connect přijímá tři generické typy, které jsme si definovaly na začátku.

Závěr


O Reactu s Typescriptem by se dalo napsat mnoho věcí. Nicméně, velikost článku by poté byl spíše kompletní příručka, což není cílem. Proto nezbývá, než toto téma rozdělit na víc částí. Příště zkusím vysvětlit, proč třeba nepoužívat funkce.bind(this) a jak se tomu vyhnout.

Komentáře

Populární příspěvky z tohoto blogu

Jak si v IT vydělat hodně peněz?

Na začátek by bylo dobré, abych objasnil samotný titulek, který může na někoho působit jako červený hadr. Článek nebude o obecných pravidlech, ale bude vyprávět můj vlastní příběh, na kterém vám zkusím ukázat, jak se dá docílit úspěchu, či alespoň správně nastartovat svojí vlastní kariéru v IT.

I když se z názvu článku dá dedukovat, že se vše bude točit kolem peněz, není tomu tak. Alespoň ze dvou třetin určitě ne. Ale to už předbíhám, pojďme to raději vzít hezky popořadě...

Kdybychom měli mluvit o roce 2017 jako o přelomové době, nejspíše to nebude pravda. I když pro někoho to může být rok plný úspěchů a štěstí v podobě narození zdravých dětí, svatby či první velké lásky, tak z pohledu lidstva se jedná o rok, který jen kopíruje předešlé a v oblasti technologií nás posouvá stejným tempem jako rok předtím.

Jsem naprosto přesvědčen o tom, že i když se současná doba tak nenazývá, tak prožíváme dobu, která jednou bude označena za revoluční, a to zejména díky vynálezu internetu, který je st…

Jak by se firmy neměly chovat k programátorům?

Každý, kdo pracuje v IT oboru, se jistě již setkal s různými „geniálními nápady“, od kterých si firma slibovala zlepšení produktivity či snížení nákladů. Ať už je to zavedení agilních principů, striktní kontrola práce či zavedení nové a skvělé metodiky, o které si „šéf“ přečetl včera na internetu. Jsou z toho skutečně tak nadšení i samotní vývojáři? A bude nový nápad fungovat?
K napsání tohoto článku mě navedly různé programátorské diskuze, kde si lidé stěžovali na firmu, kde pracují. Příklady, které zde uvedu, jsou z reálné praxe. Ať už jsem je zažil jako řadový programátor, či jako šéf týmu.
I když je poptávka po programátorech tak vysoká, že Vás headhunteři nahánějí i ve chvílích, kdy o to opravdu nestojíte, tak i přes to je mnoho lidí, kteří se bojí opustit svoje současné zaměstnání.
Čeho se nejčastěji bojíme? Je to samozřejmě nejistota, kterou si často omlouváme větami jako: „Tady mám své pohodlí, co když to jinde mít nebudu?“ nebo „I když mě to v práci štve a nebaví, tak mě ale…

Jak jsem technologicky postavil startup

Tento příběh pojednává o technologiích, nástrojích a vůbec o všem, co jsem potřeboval k tomu, abych byl schopen, postavit startup na zelené louce.

Každý správný příběh začíná stejně: "Jednou jsem...."

Kapitola první: Nápad
Jednou jsem se setkal s člověkem, který měl nápad na produkt, který se v průmyslu zatím nevyskytuje. I přes prvotní skepsi, kdy jsem si říkal: "Tohle už přeci dávno v průmyslu existuje, ne?", jsem došel ke zjištění, že nikoli.

Tím jsem se dostal ke svému prvnímu poučení. Průmysl je technologicky dost zabržděný. Osobně se domnívám, že těch důvodů, proč tomu tak je, je několik. Za prvé je to fakt, že většina lidí, kteří se pohybují v tomto odvětví jsou často konzervativní a za správné považují pouze léty osvědčené věci. Druhým důvodem je to, že jakákoli změna znamená riziko. Ať už z pohledu finanční ztráty tak i z pohledu stability výroby. No a třetím a nejzásadnějším důvodem je to, že ač zde máme spousty technologických vymožeností, narážíme na to,…