Creando tu primera aplicación web con React

React ha ganado mucha popularidad recientemente y ha atraído a una comunidad grande y activa. Esto da como resultado una gran cantidad de componentes reutilizables que le ahorrarán tiempo al codificar. La propia biblioteca fomenta la escritura de código débilmente acoplado que es modular y componible.

En este tutorial, le mostraré cómo crear una pequeña aplicación y cómo dividirla en componentes discretos que se comunican entre sí. Como base, tomaremos el ejemplo del sitio web basado en npm del mes pasado, pero lo haremos a la manera de React. Es interesante comparar los resultados:la versión React tiene algunas líneas de código más que la versión jQuery, pero ambos podemos estar de acuerdo en que está mucho mejor organizada.

Lo que necesitas saber sobre React

  • Es una biblioteca/marco popular del lado del cliente para crear interfaces de usuario, desarrollado y utilizado por Facebook.
  • Con él, organiza su aplicación en torno a componentes discretos, cada uno de los cuales maneja su propia representación y estado. Los componentes se pueden anidar unos dentro de otros.
  • React es rápido porque minimiza la cantidad de escrituras en el DOM (la parte más lenta de cualquier aplicación del lado del cliente).
  • La forma recomendada de escribir código React es usando JSX, una extensión de JavaScript que presenta los componentes como elementos HTML. JSX debe compilarse en JS para que funcione en los navegadores.
  • No ha llegado a la versión 1.0 al momento de escribir este artículo, por lo que podría haber cambios en el futuro.
  • Tenemos un buen artículo con ejemplos para aprender a reaccionar que puedes consultar. También está la guía oficial de inicio aquí.

Lo que construiremos

Crearemos una aplicación web simple, que invite a las personas a buscar ubicaciones y almacenarlas en el almacenamiento local de sus navegadores. Las ubicaciones se presentarán en un mapa de Google con la ayuda del complemento GMaps. Usaremos Bootstrap con el tema Flatly para la interfaz. En el proceso, dividiremos la aplicación en componentes lógicos y haremos que se comuniquen entre sí.

Ejecutando la demostración

Si no desea leer el tutorial completo, puede continuar y descargar el código fuente desde el botón de descarga de arriba. Para ejecutarlo, debe tener instalados Node.js y npm. Suponiendo que lo haya hecho, esto es lo que debe hacer:

  1. Descargue el zip con el código fuente desde el botón de arriba.
  2. Extráigalo a una carpeta en algún lugar de su computadora.
  3. Abra una nueva terminal (símbolo del sistema) y navegue a esa carpeta.
  4. Ejecutar npm install . Esto descargará e instalará todas las dependencias necesarias.
  5. Ejecutar npm run build . Esto compilará los componentes de reacción en un archivo JavaScript normal llamado compiled.js.
  6. Abra index.html en su navegador. Debería ver la aplicación.

Hay un comando npm más que he preparado para que su desarrollo sea más fácil:

npm run watch

Esto compilará el código JSX a JavaScript y continuará monitoreándolo en busca de cambios. Si cambia un archivo, el código se volverá a compilar automáticamente para usted. Puede ver estos comandos en package.json archivo.

El código fuente es fácil de seguir y tiene muchos comentarios, por lo que aquellos que prefieran leer el código fuente pueden omitir el resto del artículo.

Configurando las cosas

Como mencioné, la forma recomendada de escribir código React es usando una extensión de JavaScript llamada JSX, que debe transformarse a JavaScript. Hay algunas herramientas que pueden hacer esto, pero la que recomiendo es reactify, una transformación browserify. Entonces, además de compilar JSX a JavaScript, obtienes acceso a require() llamada node.js y con ella la capacidad de instalar y usar bibliotecas desde npm.

Para configurar reactify, browserify y el resto, ejecute este comando:

npm install browserify reactify watchify uglify-js react

Para crear un archivo JavaScript minimizado y listo para producción, que puede poner en línea, ejecute este comando en su terminal:

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

Reactify admite un conjunto limitado de las nuevas funciones de ES6 con el --es6 flag, que he usado en el código fuente (lo verás en un momento).

Mientras desarrolla, use el siguiente comando:

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

Watchify monitoreará sus archivos en busca de cambios y recompilará su código fuente si es necesario. También habilita los mapas de origen, por lo que puede usar el depurador de Chrome para revisar su código.

¡Excelente! Ahora puede escribir módulos React, bibliotecas require() npm e incluso usar algunas funciones de ES6. ¡Estás listo para escribir algo de código!

El código

Estos son los componentes que escribiremos:

  • Aplicación es el componente principal. Contiene métodos para las acciones que puede realizar el usuario, como buscar, agregar una ubicación a favoritos y más. Los demás componentes están anidados en su interior.
  • Ubicación actual presenta la dirección visitada actualmente en el mapa. Las direcciones se pueden agregar o eliminar de favoritos haciendo clic en el icono de estrella.
  • Lista de ubicaciones representa todas las ubicaciones favoritas. Crea un LocationItem para cada uno.
  • Elemento de ubicación es una ubicación individual. Cuando se hace clic en él, se busca su dirección correspondiente y se resalta en el mapa.
  • Mapa se integra con la biblioteca de GMaps y representa un mapa de Google Maps.
  • Buscar es un componente que envuelve el formulario de búsqueda. Cuando se envía, se activa una búsqueda de la ubicación.

Aplicación.js

Primero está la aplicación. Además de los métodos de ciclo de vida que requiere React, tiene algunos adicionales que reflejan las principales acciones que puede realizar el usuario, como agregar y eliminar una dirección de favoritos y buscar. Tenga en cuenta que estoy usando la sintaxis ES6 más corta para definir funciones en objetos.

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;

En el método render, inicializamos los otros componentes. Cada componente recibe solo los datos que necesita para realizar su trabajo, como atributos. En algunos lugares, también pasamos métodos a los que llamarán los componentes secundarios, lo cual es una buena manera para que los componentes se comuniquen mientras se mantienen aislados unos de otros.

UbicaciónActual.js

El siguiente es Ubicación actual . Este componente presenta la dirección de la ubicación que se muestra actualmente en una etiqueta H4 y un icono de estrella en el que se puede hacer clic. Cuando se hace clic en el icono, la aplicación toggleFavorite se llama al método.

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;

Lista de ubicaciones.js

Lista de ubicaciones toma la matriz con las ubicaciones favoritas que se le pasó, crea un LocationItem objeto para cada uno y lo presenta en un grupo de lista 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;

Elemento de ubicación.js

Elemento de ubicación representa una ubicación favorita individual. Utiliza la biblioteca de momentos para calcular el tiempo relativo desde que se agregó la ubicación como favorita.

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;

Mapa.js

Mapa es un componente especial. Envuelve el complemento de Gmaps, que no es un componente de React en sí mismo. Conectándose al componentDidUpdate del Mapa método, podemos inicializar un mapa real dentro del #map div cada vez que se cambia la ubicación mostrada.

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;

Búsqueda.js

La Búsqueda El componente consta de un formulario Bootstrap con un grupo de entrada. Cuando se envía el formulario, el searchForAddress de la aplicación se llama al método.

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;

principal.js

Todo lo que queda es agregar la aplicación componente a la página. Lo estoy agregando a un contenedor div con el #main id (puedes ver este elemento en index.html en el archivo zip descargable).

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

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

Además de estos archivos, he incluido la biblioteca de GMaps y la API de JavaScript de Google Maps de la que depende, como etiquetas