Vytvoření vaší první webové aplikace s Reactem

React si v poslední době získal velkou popularitu a přilákal velkou a aktivní komunitu. Výsledkem je obrovské množství opakovaně použitelných komponent, které vám ušetří čas při kódování. Samotná knihovna podporuje psaní volně spojeného kódu, který je modulární a skládá se.

V tomto tutoriálu vám ukážu, jak vytvořit malou aplikaci a jak ji rozdělit na samostatné komponenty, které spolu mluví. Jako základ vezmeme příklad webu řízeného npm z minulého měsíce, ale uděláme to způsobem React. Je zajímavé porovnat výsledky - verze React má o pár řádků kódu více než verze jQuery, ale oba se shodneme na tom, že je mnohem lépe organizovaná.

Co potřebujete vědět o React

  • Je to oblíbená knihovna/rámec na straně klienta pro vytváření uživatelských rozhraní, kterou vyvíjí a používá Facebook.
  • Pomocí této funkce organizujete svou aplikaci kolem samostatných komponent, přičemž každá zpracovává své vlastní vykreslování a stav. Komponenty mohou být vnořeny do sebe.
  • React je rychlý, protože minimalizuje počet zápisů do DOM (nejpomalejší část jakékoli klientské aplikace).
  • Doporučený způsob, jak napsat kód React, je použití JSX – rozšíření JavaScriptu, které prezentuje komponenty jako prvky HTML. JSX musí být zkompilován do JS, aby fungoval v prohlížečích.
  • V době psaní tohoto článku ještě nenarazil na verzi 1.0, takže v budoucnu mohou nastat změny.
  • Máme pěkný článek s příklady, jak se naučit reagovat, na který se můžete podívat. Zde je také oficiální příručka Začínáme.

Co budeme stavět

Vytvoříme jednoduchou webovou aplikaci, která vyzve lidi, aby vyhledávali místa a ukládali je do localStorage jejich prohlížečů. Místa budou prezentována na mapě Google pomocí pluginu GMaps. Pro rozhraní použijeme Bootstrap s motivem Flatly. V tomto procesu rozdělíme aplikaci na logické komponenty a přimějeme je, aby spolu mluvily.

Spuštění ukázky

Pokud nechcete číst celý tutoriál, můžete pokračovat a stáhnout si zdrojový kód pomocí tlačítka Stáhnout výše. Pro jeho spuštění je potřeba mít nainstalovaný Node.js a npm. Za předpokladu, že ano, musíte udělat toto:

  1. Stáhněte si zip se zdrojovým kódem z tlačítka výše.
  2. Rozbalte jej do složky někde v počítači.
  3. Otevřete nový terminál (příkazový řádek) a přejděte do této složky.
  4. Spustit npm install . Tím stáhnete a nainstalujete všechny potřebné závislosti.
  5. Spustit npm run build . Tím se komponenty reakce zkompilují do běžného souboru JavaScript s názvem compiled.js.
  6. V prohlížeči otevřete soubor index.html. Měli byste vidět aplikaci.

Je tu ještě jeden příkaz npm, který jsem pro vás připravil, aby vám usnadnil vývoj:

npm run watch

Tím se zkompiluje kód JSX do JavaScriptu a bude se nadále sledovat, zda nedošlo ke změnám. Pokud soubor změníte, kód se vám automaticky znovu zkompiluje. Tyto příkazy můžete vidět v package.json soubor.

Zdrojový kód je snadno sledovatelný a má spoustu komentářů, takže pro ty z vás, kteří si raději přečtou zdroj, můžete zbytek článku přeskočit.

Nastavení věcí

Jak jsem již zmínil, doporučený způsob psaní kódu React je pomocí rozšíření JavaScriptu s názvem JSX, které je třeba transformovat na JavaScript. Existuje několik nástrojů, které to umí, ale ten, který doporučuji, je reify – transformace pomocí prohlížeče. Takže kromě kompilace JSX do JavaScriptu získáte přístup k require() volání node.js as ním i možnost instalovat a používat knihovny z npm.

Chcete-li nastavit reify, browserify a ostatní, spusťte tento příkaz:

npm install browserify reactify watchify uglify-js react

Chcete-li vytvořit produkční připravený a miniifikovaný soubor JavaScript, který můžete umístit online, spusťte ve svém terminálu tento příkaz:

NODE_ENV=production browserify -t [ reactify --es6 ] main.js | uglifyjs > compiled.min.js

Reactify podporuje omezenou sadu nových funkcí ES6 s --es6 flag, který jsem použil ve zdrojovém kódu (za chvíli ho uvidíte).

Při vývoji použijte následující příkaz:

watchify -v -d -t [ reactify --es6 ] main.js -o compiled.js

Watchify bude sledovat změny vašich souborů a v případě potřeby znovu zkompiluje váš zdrojový kód. Umožňuje také zdrojové mapy, takže k procházení kódu můžete použít Chrome Debugger.

Skvělý! Nyní můžete psát moduly React, vyžadovat knihovny npm a dokonce používat některé funkce ES6. Jste připraveni napsat nějaký kód!

Kód

Zde jsou komponenty, které budeme psát:

  • Aplikace je hlavní složkou. Obsahuje metody pro akce, které může uživatel provádět, jako je vyhledávání, přidávání místa do oblíbených a další. Ostatní komponenty jsou v něm vnořeny.
  • Aktuální poloha zobrazuje aktuálně navštívenou adresu na mapě. Adresy lze přidat nebo odebrat z oblíbených kliknutím na ikonu hvězdičky.
  • LocationList vykreslí všechna oblíbená místa. Pro každý vytvoří položku LocationItem.
  • LocationItem je individuální lokalita. Když na něj kliknete, vyhledá se jeho odpovídající adresa a zvýrazní se v mapě.
  • Mapa integruje se s knihovnou GMaps a vykresluje mapu z Map Google.
  • Hledat je komponenta, která obtéká vyhledávací formulář. Po odeslání se spustí vyhledávání místa.

App.js

První na řadě je aplikace. Kromě metod životního cyklu, které React vyžaduje, má několik dalších, které odrážejí hlavní akce, které může uživatel provádět, jako je přidávání a odebírání adresy z oblíbených a vyhledávání. Všimněte si, že pro definování funkcí v objektech používám kratší syntaxi ES6.

var React = require('react');

var Search = require('./Search');
var Map = require('./Map');
var CurrentLocation = require('./CurrentLocation');
var LocationList = require('./LocationList');

var App = React.createClass({

    getInitialState(){

        // Extract the favorite locations from local storage

        var favorites = [];

        if(localStorage.favorites){
            favorites = JSON.parse(localStorage.favorites);
        }

        // Nobody would get mad if we center it on Paris by default

        return {
            favorites: favorites,
            currentAddress: 'Paris, France',
            mapCoordinates: {
                lat: 48.856614,
                lng: 2.3522219
            }
        };
    },

    toggleFavorite(address){

        if(this.isAddressInFavorites(address)){
            this.removeFromFavorites(address);
        }
        else{
            this.addToFavorites(address);
        }

    },

    addToFavorites(address){

        var favorites = this.state.favorites;

        favorites.push({
            address: address,
            timestamp: Date.now()
        });

        this.setState({
            favorites: favorites
        });

        localStorage.favorites = JSON.stringify(favorites);
    },

    removeFromFavorites(address){

        var favorites = this.state.favorites;
        var index = -1;

        for(var i = 0; i < favorites.length; i++){

            if(favorites[i].address == address){
                index = i;
                break;
            }

        }

        // If it was found, remove it from the favorites array

        if(index !== -1){

            favorites.splice(index, 1);

            this.setState({
                favorites: favorites
            });

            localStorage.favorites = JSON.stringify(favorites);
        }

    },

    isAddressInFavorites(address){

        var favorites = this.state.favorites;

        for(var i = 0; i < favorites.length; i++){

            if(favorites[i].address == address){
                return true;
            }

        }

        return false;
    },

    searchForAddress(address){

        var self = this;

        // We will use GMaps' geocode functionality,
        // which is built on top of the Google Maps API

        GMaps.geocode({
            address: address,
            callback: function(results, status) {

                if (status !== 'OK') return;

                var latlng = results[0].geometry.location;

                self.setState({
                    currentAddress: results[0].formatted_address,
                    mapCoordinates: {
                        lat: latlng.lat(),
                        lng: latlng.lng()
                    }
                });

            }
        });

    },

    render(){

        return (

            <div>
                <h1>Your Google Maps Locations</h1>

                <Search onSearch={this.searchForAddress} />

                <Map lat={this.state.mapCoordinates.lat} lng={this.state.mapCoordinates.lng} />

                <CurrentLocation address={this.state.currentAddress} 
                    favorite={this.isAddressInFavorites(this.state.currentAddress)} 
                    onFavoriteToggle={this.toggleFavorite} />

                <LocationList locations={this.state.favorites} activeLocationAddress={this.state.currentAddress} 
                    onClick={this.searchForAddress} />

            </div>

        );
    }

});

module.exports = App;

V metodě render inicializujeme ostatní komponenty. Každá komponenta obdrží pouze data, která potřebuje ke své práci, jako atributy. Na některých místech také předáváme metody, které budou volat podřízené komponenty, což je dobrý způsob, jak komponenty komunikovat a zároveň je udržovat navzájem izolované.

CurrentLocation.js

Další je Aktuální poloha . Tato komponenta představuje adresu aktuálně zobrazeného místa ve značce H4 a ikonu hvězdičky, na kterou lze kliknout. Po kliknutí na ikonu se zobrazí toggleFavorite aplikace se nazývá metoda.

var React = require('react');

var CurrentLocation = React.createClass({

    toggleFavorite(){
        this.props.onFavoriteToggle(this.props.address);
    },

    render(){

        var starClassName = "glyphicon glyphicon-star-empty";

        if(this.props.favorite){
            starClassName = "glyphicon glyphicon-star";
        }

        return (
            <div className="col-xs-12 col-md-6 col-md-offset-3 current-location">
                <h4 id="save-location">{this.props.address}</h4>
                <span className={starClassName} onClick={this.toggleFavorite} aria-hidden="true"></span>
            </div>
        );
    }

});

module.exports = CurrentLocation;

LocationList.js

LocationList vezme pole s oblíbenými umístěními, které mu bylo předáno, vytvoří LocationItem objekt pro každý a prezentuje jej ve skupině seznamu Bootstrap.

var React = require('react');
var LocationItem = require('./LocationItem');

var LocationList = React.createClass({

    render(){

        var self = this;

        var locations = this.props.locations.map(function(l){

            var active = self.props.activeLocationAddress == l.address;

            // Notice that we are passing the onClick callback of this
            // LocationList to each LocationItem.

            return <LocationItem address={l.address} timestamp={l.timestamp} 
                    active={active} onClick={self.props.onClick} />
        });

        if(!locations.length){
            return null;
        }

        return (
            <div className="list-group col-xs-12 col-md-6 col-md-offset-3">
                <span className="list-group-item active">Saved Locations</span>
                {locations}
            </div>
        )

    }

});

module.exports = LocationList;

LocationItem.js

LocationItem představuje individuální oblíbené místo. K výpočtu relativního času od přidání místa jako oblíbeného používá knihovnu momentů.

var React = require('react');
var moment = require('moment');

var LocationItem = React.createClass({

    handleClick(){
        this.props.onClick(this.props.address);
    },

    render(){

        var cn = "list-group-item";

        if(this.props.active){
            cn += " active-location";
        }

        return (
            <a className={cn} onClick={this.handleClick}>
                {this.props.address}
                <span className="createdAt">{ moment(this.props.timestamp).fromNow() }</span>
                <span className="glyphicon glyphicon-menu-right"></span>
            </a>
        )

    }

});

module.exports = LocationItem;

Map.js

Mapa je speciální komponenta. Obaluje plugin Gmaps, který sám o sobě není součástí Reactu. Připojením k componentDidUpdate mapy můžeme inicializovat skutečnou mapu uvnitř #map div při každé změně zobrazeného umístění.

var React = require('react');

var Map = React.createClass({

    componentDidMount(){

        // Only componentDidMount is called when the component is first added to
        // the page. This is why we are calling the following method manually. 
        // This makes sure that our map initialization code is run the first time.

        this.componentDidUpdate();
    },

    componentDidUpdate(){

        if(this.lastLat == this.props.lat && this.lastLng == this.props.lng){

            // The map has already been initialized at this address.
            // Return from this method so that we don't reinitialize it
            // (and cause it to flicker).

            return;
        }

        this.lastLat = this.props.lat;
        this.lastLng = this.props.lng

        var map = new GMaps({
            el: '#map',
            lat: this.props.lat,
            lng: this.props.lng
        });

        // Adding a marker to the location we are showing

        map.addMarker({
            lat: this.props.lat,
            lng: this.props.lng
        });
    },

    render(){

        return (
            <div className="map-holder">
                <p>Loading...</p>
                <div id="map"></div>
            </div>
        );
    }

});

module.exports = Map;

Search.js

Vyhledávání komponenta se skládá z formuláře Bootstrap se vstupní skupinou. Po odeslání formuláře searchForAddress aplikace se nazývá metoda.

var React = require('react');

var Search = React.createClass({

    getInitialState() {
        return { value: '' };
    },

    handleChange(event) {
        this.setState({value: event.target.value});
    },

    handleSubmit(event){

        event.preventDefault();

        // When the form is submitted, call the onSearch callback that is passed to the component

        this.props.onSearch(this.state.value);

        // Unfocus the text input field
        this.getDOMNode().querySelector('input').blur();
    },

    render() {

        return (
            <form id="geocoding_form" className="form-horizontal" onSubmit={this.handleSubmit}>
                <div className="form-group">
                    <div className="col-xs-12 col-md-6 col-md-offset-3">
                        <div className="input-group">
                            <input type="text" className="form-control" id="address" placeholder="Find a location..." 
                            value={this.state.value} onChange={this.handleChange} />
                            <span className="input-group-btn">
                                <span className="glyphicon glyphicon-search" aria-hidden="true"></span>
                            </span>
                        </div>
                    </div>
                </div>
            </form>
        );

    }
});

module.exports = Search;

main.js

Zbývá pouze přidat aplikaci komponentu na stránku. Přidávám jej do kontejneru div s #main id (tento prvek můžete vidět v index.html v souboru zip ke stažení).

var React = require('react');
var App = require('./components/App');

React.render(
  <App />,
  document.getElementById('main')
);

Kromě těchto souborů jsem zahrnul knihovnu GMaps a Google Maps JavaScript API, na kterých závisí, jako značky