Usprawniamy pracę z React dzięki Babel stage-2

Jakiś czas temu postanowiłem zmienić swój stack i przekonałem się do tworzenia back-endu w node, natomiast front projektuję przy użyciu React. Dokumentacja React uważam, że jest bardzo dobra, ale chciałbym dzisiaj omówić ciekawe rozwiązanie jakim jest preset stage-2 do transpilatora Babel.

Bardziej doświadczeni programiści React pewnie znają go doskonale. Ja trochę dozą przypadku miałem okazję poznać stage-2 na początku swojej przygody z React i myślę, że wielu początkującym front-endowcom Reacta przyda się jego znajomość.

Generalnie preset stage-2 umożliwia stosowanie składni normalnie niedozwolonej wewnątrz definiowania klasy (może z czasem się to zmieni i nowe wersje ECMAScript natywnie uwzględnią taką składnię). Pozwala to uprościć część zadań jakie wykonuje się podczas tworzenia komponentów React.

Aby móc korzystać ze stage-2 należy dodać odpowiedni preset w ustawieniach Babel, np.:

{
  "presets": ["stage-2"]
}

oraz oczywiście zainstalować go np. z pakietu npm:

npm install --save-dev babel-preset-stage-2

Wstępne ustawianie stanu komponentu

Aby ustawić początkowy stan komponentu standardowo określamy obiekt state w konstruktorze:

class UserInfo extends React.Component {
    constructor() {
        this.state = {
            name: props.name
        }
    }

    render() {
        const { name } = this.state;
        
        return (
           <p>{ name }</p>         
        );
    }
}

Zapis ten można jednak nieco uprościć poprzez użycie składni stage-2 jak w poniższym przykładzie:

class UserInfo extends React.Component {
    state = {
        name: this.props.name //teraz z "this"
    }
    
    render() {
        const { name } = this.state;
        
        return (
           <p>{ name }</p>         
        );
    }
}

Trzeba jednak pamiętać, że w tym wypadku (wiążąc stateprops) ustawiając wartość właściwości state.name musimy użyć dodatkowo wskaźnika this (this.props.name a nie samo props.name). Jeśli nie planujemy w konstruktorze wykonywać więcej operacji to taki zapis wydaje się czytelniejszy i prostszy. Oczywiście nie dotyczy to sytuacji, gdy używamy redux, gdzie operacje na obiekcie stanu wyglądają nieco inaczej.

Ustawianie propTypes i defaultProps

Kolejne ułatwienie to lepszy sposób definiowania typu właściwości komponentu oraz ich wartości domyślnych. Sposób standardowy, wykorzystujący składnię class wymaga ustawienia właściwości propTypes oraz defaultProps po za definicją samej klasy. Nie będziemy w tej chwili dokładnie analizować dlaczego tak właśnie się to odbywa – jeśli ktoś jest zainteresowany tą tematyką to polecam poczytać o tzw. funkcjach konstruktorach i ich podobieństwach z klasami (np. Funkcje konstruktory vs klasy w JavaScript).

class UserInfo extends React.Component {
    render() {
        return (
           <p>{ this.props.name }</p>         
        );
    }
}

UserInfo.propTypes = {
    name: PropTypes.string
};

UserInfo.defaultProps = {
    name: 'Tomek'
};

Takie podejście wymaga jednak trzykrotnego podania nazwy tworzonego komponentu (a dokładniej tworzonej klasy), o czym trzeba pamiętać jeśli z jakiś powodów będziemy chcieli zmienić nazwę komponentu. Ponad to jest to pewnego rodzaju wydzielenie tych ustawień z właściwej definicji wnętrza klasy.

Tutaj również składnia stage-2 daje nam ciekawą możliwość trzymania wszystkich najważniejszych dla komponentu informacji w jednym miejscu, czyli w definicji klasy. Zwróć uwagę, że podobnie jak poprzednio definiujemy właściwość propTypes oraz defaultProps ale tym razem musimy użyć dodatkowo słowa static (którego nie było podczas ustawiania początkowego stanu obiektu state).

Użycie słowa static dodaje wskazane właściwości do prototypu UserInfo, przez co nie są one tworzone indywidualnie dla każdej instancji obiektu UserInfo (czyli de facto dla każdego użycia tego komponentu). Stan komponentu jest często zmienny dla każdej jego instancji dlatego nie mogliśmy użyć składni: static state = {}.

class UserInfo extends React.Component {

    static propTypes = {
        name: PropTypes.string
    }

    static defaultProps = {
        name: 'Tomek'
    }

    render() {
        return (
            <p>{ this.props.name }</p>
        );
    }
}

Kolejną zaletą takiej składni jest fakt, że tworząc klasę (komponent) możemy od razu na początku wskazać jego stan początkowy, wymagane typy właściwości oraz ich ewentualne wartości domyślne. Pozwala to szybciej i wygodniej analizować komponenty i wyszukiwać interesujące nas informacje.

Koniec z ręcznym bindowaniem metod komponentu

Osobiście uważam jednak, że największą zaletą stage-2 jest uproszczenie definiowania wewnętrznych metod komponentu, używanych np. do obsługi różnego rodzaju zdarzeń, pobierania danych z serwera itp.

Metodą standardową tworzyliśmy funkcję onClick w sposób dopuszczony przez standard ECMAScript 6. Problemem jednak było odpowiednie wskazanie wewnątrz metody render funkcji onClick. Najczęściej rozwiązywało się to przy użyciu tzw. bindowania, czyli jawnego wskazania obiektu, na który wskazuje w danej funkcji wskaźnik this.

W przypadku jednej czy dwóch funkcji nie stanowi to problemu, ale jeśli metod obsługi zdarzeń czy innych funkcji mamy więcej to należy pamiętać, aby po jej zaprojektowaniu „dodać” jej bindowanie w konstruktorze. Czasami  trakcie projektowania komponentu zdarza się, że chcemy zmienić nazwę funkcji i może okazać się, że przez nieuwagę zapomnimy o konstruktorze…

class UserInfo extends React.Component {

    constructor() {
        this.onClick = this.onClick.bind( this );
    }

    onClick() {
        //rób coś po kliknięciu...
    }

    render() {
        return (
            <p onClick={ this.onClick }>
                Kliknij mnie!
            </p>
        );
    }
}

Po co jednak komplikować sobie życie, skoro można użyć prostej składni arrow function do zdefiniowania metody wewnątrz klasy:

class UserInfo extends React.Component {

    onClick = () => {
        //rób coś po kliknięciu...
    }

    render() {
        return (
            <p onClick={ this.onClick }>
                Kliknij mnie!
            </p>
        );
    }
}

W tym wypadku this zawsze wskazuje na obiekt komponentu. To jedno z miejsc, gdzie uwidacznia się moc funkcji strzałkowych, które wcale nie powstały w celu krótszego zapisu, lecz właśnie w celu precyzyjnego i przewidywalnego odniesienia dla wskaźnika this.

Funkcje te możemy deklarować tak jak inne funkcje strzałkowe, czyli dopuszczalne jest pominięcie nawiasów klamrowych lecz wtedy od razu wykonamy instrukcję return. Wiele osób zaleca aby zawsze (lub prawie zawsze) stosować zapis z klamerkami, nawet jeśli wewnątrz znajduje się tylko jedna instrukcja – return. Szczególnie warto unikać nadmiernego zagnieżdżania arrow function bez klamerek, czyli de facto z zagnieżdżonymi instrukcjami return. Rozwiązanie takie jest często mało czytelne i trudne w analizie.

W metodzie render komponentu do funkcji odwołujemy się w obu przypadkach tak samo, czyli poprzez this.onClick.

Podsumowanie

Osoby bardziej doświadczone w pracy z React pewnie raczej nie dotrą do tego miejsca gdyż możliwości stage-2 są im doskonale znane. Artykuł jest więc głównie dedykowany osobom zaczynającym swoją przygodę z React.

Być może z czasem omawiana tutaj składnia wejdzie bezpośrednio do standardu ECMAScript ale nie ma co gdybać i zbyt mocno przewidywać przyszłości (już mieliśmy próbkę tego z książkami o PHP6…). Trzeba po prostu poczekać… a na razie cieszyć się Babel+stage-2 🙂

PS. Dla osób zaczynających React szczerze polecam zestaw artykułów na stronie nafrontendzie, skąd sam czerpałem wiele cennych wskazówek podczas nauki.

  • elwood

    Sam używam tego rozwiązania, ale trzeba zauważyć, że metody tworzone za pomocą strzałek, są dodawane jako metody konstruktora, a nie do prototypów. Czy jest to jakiś duży minus? To już zostaje do ocenienia dla każdego z osobna. Myślę że warto o tym wiedzieć.

    • Szczerze mówiąc to myślę, że w tym wypadku nie jest to aż tak istotne i warto wziąć pod uwagę wygodniejsze pisanie kodu. Ale tak jak piszesz, to już niech każdy sam sobie oceni co mu odpowiada bardziej, a co mniej.

  • Widzisz, już jakiś czas nie analizowałem postępów w rozwoju JS 🙂 Widzę też, co mnie cieszy, że powolutku regexp zbliża się do tych znanych np. z Java, m.in. lookbehind czy named captured gropus. Ale nie ma co gdybać, jak wejdzie do standardu i zostanie zaimplementowane to wtedy będziemy mieć pewność. Widzę, że na stage 1 jest też składnia generator arrow function, ciekawe jak to wszystko się rozwinie w biegiem lat 🙂

  • Własności klas, wraz z metodami prywatnymi, są już na stage 3. Szanse, że nie wejdzie to do standardu, są IMO bardzo małe. Oczekiwałbym tego być może już w ES2018.