React : La Révolution des Views (?) (cÎté front)

Nous parlerons de:

  • Installation des prĂ©requis : Node, Express, Pulldown 

  • Ecrire nos composants React
  • La mĂȘme chose mais avec l’aide de RequireJS

BriĂšvement, React, qu’est-ce que c’est ? C’est tout simplement une librairie javascript issue de chez Facebook pour dĂ©velopper des IHM. La particularitĂ© de React, c’est d’utiliser sa propre implĂ©mentation du Virtual DOM. Il a Ă©tĂ© dĂ©veloppĂ© en pensant performance, il n’adresse que la partie “View” et pour information l’ihm de la version web d’Instagram est entiĂšrement dĂ©veloppĂ©e avec React (et quelques parties de Facebook aussi).

Codes sources de l’article

J’ai Ă©crit cet article en une seule passe sans m’arrĂȘter, il mĂ©rite certainement encore quelques relectures, mais je ne rĂ©siste pas Ă  la tentation de le publier en l’état.

Avertissements

Attention ça peut piquer les yeux

Alors, avant de commencer, il faut savoir que React propose une syntaxe “trĂšs (trĂšs)particuliĂšre” : vous allez mĂ©langer votre code HTML (en fait c’est une sorte de XML appelĂ© JSX) avec votre code javascript!!! LĂ  je vais encore me prendre une brassĂ©e par quelques intĂ©grateurs html, et peut-ĂȘtre mĂȘme par des puristes du javascript, mais peu importe, si Facebook le fait, ça vaut peut-ĂȘtre au minimum le coup d’y jeter un coup d’Ɠil. Et il est probablement temps que les 2 mĂ©tiers fassent quelques pas l’un vers l’autre et rĂ©flĂ©chisse Ă  la maniĂšre de faire Ă©voluer leurs mĂ©tiers respectifs.

Notre 1er composant va ressembler Ă  ceci :

var HelloTitle = React.createClass({

  render: function() {
    return (
      <div>
        <h1>
          {this.props.message}
        </h1>
        <h3>Version {this.props.version}</h3>
        <hr></hr>
      </div>
    );
  }
});

Ça calme! non? ;) Prenez un peu de temps et visionnez ceci http://www.youtube.com/watch?v=x7cQ3mrcKaY

L’utilisation de JSX n’est pas obligatoire, vous pouvez faire du pur javascript, mais JSX est beaucoup plus simple Ă  utiliser et facilite les travaux d’intĂ©gration.

Attention 
 again

Mon javascript ne sera pas compatible tout navigateur 


Je vais utiliser des espùces de “custom tags”, parce que chez Google et Mozilla ils pensent que c’est l’avenir :

Et j’ai encore moins de scrupule à le faire quand Douglas Crockford en parle aussi : http://www.crockford.com/html/.

Si ça vous choque, ne me lisez pas ou entamons une discussion (calme ;)) et imaginons notre futur avec des React, Polymer, X-Tags, 


Allez, on attaque.

Pré-requis

Nous allons dans un 1er temps créer la stack de travail :

Création de la partie serveur : Node

Nous allons utiliser le framework Express : http://expressjs.com/

Dans votre répertoire projet, créez un fichier package.json :

{
  "name": "hello-react",
  "description": "hello-react app",
  "version": "0.0.1",
  "dependencies": {
    "express": "3.x",
    "node-uuid" : "x.x"
  }
}

Puis, tapez (nous sommes dans la console) npm install pour installer les dépendances

Remarque: node-uuid permet de générer des identifiants uniques.

Ensuite, un peu de code applicatif, c’est Ă  dire les “API” qui nous serviront Ă  fournir de la data Ă  notre front js, crĂ©ez dans votre rĂ©pertoire un fichier app.js avec le code suivant:

var express = require('express');
var uuid = require('node-uuid');
var app = express()

var buddies = [
  {id : uuid.v1(), name : "Bob Morane"},
  {id : uuid.v1(), name : "Doctor Who"},
  {id : uuid.v1(), name : "Fox Mulder"},
  {id : uuid.v1(), name : "Lady Penelope"}
];

app.use(express.static(__dirname + '/public'));
app.use(express.json());
app.use(express.urlencoded());

// get all buddies
app.get("/buddies", function(req, res) {
  res.send(buddies);
});

// create a buddy
app.post("/buddies", function(req, res) {
  var buddy = req.body
  buddy.id = uuid.v1();
  buddies.push(buddy);
  res.send(buddy);
});

app.listen(3000)

Création de la partie front

# Les librairies javascript du projet

Nous allons utiliser :

Cette fois-ci nous allons faire plus simple que la derniĂšre fois avec Bower), nous allons utiliser Pulldown : https://npmjs.org/package/pulldown qui est un simple “downloader” de script, extrĂȘmement pratique.

Pour installer Pulldown : sudo npm install -g pulldown

Ensuite, créez un répertoir public, puis créez un fichier loadjs.sh que vous rendrez exécutable (chmod a+x loadjs) avec le contenu suivant :

#  === jQuery ======================
#    http://jquery.com/
#  =================================
pulldown jquery -o js/vendors

#  === react =========================
#    http://facebook.github.io/react
#  ===================================
pulldown "http://cdnjs.cloudflare.com/ajax/libs/react/0.8.0/JSXTransformer.js" -o js/vendors
pulldown "http://cdnjs.cloudflare.com/ajax/libs/react/0.8.0/react.min.js" -o js/vendors

#  === Skeleton ======================
#    http://www.getskeleton.com/
#  ===================================

pulldown "https://raw2.github.com/dhg/Skeleton/master/stylesheets/base.css" -o stylesheets
pulldown "https://raw2.github.com/dhg/Skeleton/master/stylesheets/layout.css" -o stylesheets
pulldown "https://raw2.github.com/dhg/Skeleton/master/stylesheets/skeleton.css" -o stylesheets

Dans le répertoire public, lancez ./loadjs.sh

# Remarques :

  • la partie concernant Skeleton, n’est pas obligatoire, c’est un framework css pour faire “joli”
  • si vous ĂȘtes sous Windows, pour utiliser Pulldown, crĂ©ez un fichier load.cmd, copiez les commandes “pulldown” ci-dessus et ajoutez call avant chaque pulldown, par exemple : call pulldown jquery -o js/vendors

# Préparation de la page index.html

Créez dans public le fichier index.html avec le code suivant :

<!DOCTYPE html>
<head>

  <meta charset="utf-8">
  <title>Hello React</title>
  <meta name="description" content="React tutorial">
  <meta name="author" content="@k33g_org">

  <link rel="stylesheet" href="stylesheets/base.css">
  <link rel="stylesheet" href="stylesheets/skeleton.css">
  <link rel="stylesheet" href="stylesheets/layout.css">

</head>
<body>

<div class="container">
  <div class="sixteen columns alpha">
    <h1>Hello React</h1>
  </div>
  <div class="six columns alpha">
    <h2>Buddies List</h2>
  </div>
  <div class="six columns omega">
    <h2>Buddies Form</h2>
  </div>

</div>

<script src="js/vendors/jquery.min.js"></script>
<script src="js/vendors/JSXTransformer.js"></script>
<script src="js/vendors/react.min.js"></script>
</body>
</html>

C’est parti, on fait du React

1er composant : un titre paramétrable

Remarque prĂ©liminaire: nous allons faire de la transformation “online” du code React. Si vous souhaitez prĂ©-compiler votre code pour optimiser votre web application, utilisez ceci : http://facebook.github.io/react/docs/getting-started.html# offline-transform.

Dans public créez un répertoire components, et dans ce répertoire un fichier HelloTitle.js :

/** @jsx React.DOM */

var HelloTitle = React.createClass({

  render: function() {
    return (
      <div>
        <h1>
          {this.props.message}
        </h1>
        <h3>Version {this.props.version}</h3>
        <hr></hr>
      </div>
    );
  }
});

Attention: ne pas oublier /** @jsx React.DOM */ en en-tĂȘte de fichier.

  • this.props.message : le composant a une propriĂ©tĂ© message, this.props “porte” les propriĂ©tĂ©s du composant (de la mĂȘme maniĂšre version est aussi une propriĂ©tĂ© du composant HelloTitle)
  • Non, ce n’est pas une erreur, dans le return de la mĂ©thode render, ce n’est pas une string mais bel et bien une sorte de pseudo HTML, en fait c’est plutĂŽt du XML, il faut donc bien fermer tous les tags (comme pour <hr></hr>), dans le cas de React on parle de syntaxe JSX (JavaScript XML : http://facebook.github.io/react/docs/jsx-in-depth.html).

Maintenant, je souhaite positionner mon composant dans ma page HTML. J’ajoute un tag <hello-title></hello-title> dans lequel viendra s’insĂ©rer le code gĂ©nĂ©rĂ© de notre composant :

<div class="container">
  <div class="sixteen columns alpha">
    <hello-title></hello-title>
  </div>
  <!-- ... -->

</div>

Remarque : j’aurais pu tout aussi bien faire quelque chose comme ceci : <div id="hello-title"></div>, mais d’un point de vue lisibilitĂ© je prĂ©fĂšre ma mĂ©thode.

Il nous reste plus qu’à Ă©crire le bout de code qui dĂ©clenche le rendu de notre composant :

Créez à la racine de public/js un fichier main.js :

/** @jsx React.DOM */

var messageTitle = "Hello World! Hello React!";
var version = "1.0";

React.renderComponent(
  <HelloTitle message={messageTitle} version={version}/>,
  document.querySelector('hello-title')
);
  • Ne pas oublier /** @jsx React.DOM */
  • C’est ici que l’on passe les valeurs des propriĂ©tĂ©s du composant
  • Si j’avais utilisĂ© <div id="hello-title"></div>, il faudrait Ă©crire document.querySelector('# hello-title') au lieu de document.querySelector('hello-title')

Retournons une derniÚre fois dans index.html pour déclarer notre composant, juste avant le tag de fermeture </body> :

<script type="text/jsx" src="js/components/HelloTitle.js"></script>
<script type="text/jsx" src="js/main.js"></script>
  • Notez bien type="text/jsx" dans les inclusion de script, cela permet au “transpiler” JSX d’utiliser nos scripts.

Vous pouvez maintenant démarrer votre application : node app.js et ouvrez dans votre navigateur préféré : http://localhost:3000.

Alt "001.png"

2 ùme composant : la liste des “buddies”

Nous allons afficher la liste des “buddies” fournis par notre application Express :

Dans public/components, créez un fichier HelloBuddies.js :

/** @jsx React.DOM */

  /*--- Buddies List ---*/
  var HelloBuddies = React.createClass({

    getInitialState: function() {
      return {data : [], message : "..."};
    },
    getAllBuddies : function() {

      $.get("buddies", function(buddies) {
        this.setState({data : buddies, message : Date()});
      }.bind(this));

    },
    componentWillMount: function() {
      setInterval(this.getAllBuddies, this.props.pollInterval);
    },

    render: function() {

      var buddyItem = this.state.data.map(function(buddy){
        return (<li>
           {buddy.id} {" "} <strong>{buddy.name}</strong>
        </li>);
      });

      return (
        <div><h2>Buddies List</h2>
          <strong>{this.state.message}</strong>
          <ul className="square">
            {buddyItem}
          </ul>
        </div>
        );
    }
  });
  • getInitialState() est une mĂ©thode de React qui permet de dĂ©finit l’état initial des variables “attachĂ©es” aux composants
  • Pour ensuite modifier ces variables on utilisera this.setState({le_nom_de_la_variable:valeur})
  • Pour lire le contenu de ces variables : this.state.le_nom_de_la_variable
  • getAllBuddies() est une fonction utilisateur : c’est elle qui va faire une requĂȘte de type REST au serveur pour obtenir la liste des buddies et modifier this.state.data quand elle recevra les donnĂ©es
  • componentWillMount() est une mĂ©thode React, on peut l’assimiler Ă  une sorte d’init() ou initialize() qui est dĂ©clenchĂ©e Ă  la crĂ©ation du composant. Il existe aussi componentDidMount() une fois que le composant est “ancrĂ©â€ dans le DOM. Dans componentWillMount() nous demandons d’exĂ©cuter getAllBuddies() toutes les N (this.props.pollInterval) millisecondes. Ainsi, Ă  chaque fois que les variables (dans state) du composant sont modifiĂ©es, la mĂ©thode render sera appelĂ©e.

Nous modifions notre code html de la maniĂšre suivante avec le tag <hello-buddies></hello-buddies> :

<div class="container">
  <div class="sixteen columns alpha">
    <hello-title></hello-title>
  </div>
  <div class="six columns alpha">
    <hello-buddies></hello-buddies>
  </div>  
  <!-- ... -->

</div>

Tout en n’oubliant pas de dĂ©clarer notre nouveau composant :

<script type="text/jsx" src="js/components/HelloTitle.js"></script>
<script type="text/jsx" src="js/components/HelloBuddies.js"></script>
<script type="text/jsx" src="js/main.js"></script>

Il ne reste plus qu’à dĂ©clarer le composant et le rendu de celui-ci dans main.js :

/** @jsx React.DOM */

var messageTitle = "Hello World! Hello React!";
var version = "1.0";

React.renderComponent(
  <HelloTitle message={messageTitle} version={version}/>,
  document.querySelector('hello-title')
);

React.renderComponent(
  <HelloBuddies pollInterval={1000}/>,
  document.querySelector('hello-buddies')
);

Remarque: notez la dĂ©claration de la valeur de l’intervalle de pooling au sein du composant (pollInterval)

Vous pouvez rafraĂźchir votre page :

Alt "002.png"

3 Úme et dernier composant : le formulaire pour créer des buddies

Dans public/components, créez un fichier HelloBuddyForm.js :

/** @jsx React.DOM */

var HelloBuddyForm = React.createClass({

  getInitialState: function() {
    return {data : [], message : ""};
  },

  handleSubmit : function() {

    var name = this.refs.name.getDOMNode().value.trim();

    if (!name) {return false;}

    this.setState({
      message : "Please wait ..."
    });

    $.ajax({
      type: "POST",
      url: "buddies",
      data: {name : name},
      dataType: "json",
      success: function(buddy){
        this.setState({
          message : buddy.id + " added!"
        });

        this.refs.name.getDOMNode().value = '';
        this.refs.name.getDOMNode().focus();

      }.bind(this)
    });

    return false;
  },


  render: function() {

    return (
      <div>
        <h2>Buddy Form</h2>
        <form onSubmit={this.handleSubmit}>
          <input type="text" placeholder="name" ref="name"/>

          <input type="submit" value="Add Buddy" />
          <br></br>
          <strong>{this.state.message}</strong>
        </form>
      </div>
      );
  }
});
  • Dans le code JSX du composant, si l’on souhaite que les Ă©lĂ©ments du DOM soit accessibles Ă  partir du code javascript du composant, il faut leur ajouter un attribut ref avec le nom du composant : <input type="text" placeholder="name" ref="name"/> et ensuite React vous permet d’interagir avec de la maniĂšre suivante : this.refs.name.getDOMNode()
  • Donc dans notre exemple nous avons un champ texte <input> avec une “rĂ©fĂ©rence” name et nous pouvons accĂ©der Ă  l’ensemble de ses propriĂ©tĂ©s (et les modifier) et de ses mĂ©thodes (ex: focus())

Une fois notre composant écrit, nous allons le déclarer dans la page html avec le tag <hello-buddy-form>:

<div class="container">
  <div class="sixteen columns alpha">
    <hello-title></hello-title>
  </div>
  <div class="six columns alpha">
    <hello-buddies></hello-buddies>
  </div>  
  <div class="six columns omega">
    <hello-buddy-form></hello-buddy-form>
  </div>

</div>

Et en ajoutant :

<script type="text/jsx" src="js/components/HelloTitle.js"></script>
<script type="text/jsx" src="js/components/HelloBuddies.js"></script>
<script type="text/jsx" src="js/components/HelloBuddyForm.js"></script>
<script type="text/jsx" src="js/main.js"></script>

Et enfin dans main.js :

/** @jsx React.DOM */

var messageTitle = "Hello World! Hello React!";
var version = "1.0";

React.renderComponent(
  <HelloTitle message={messageTitle} version={version}/>,
  document.querySelector('hello-title')
);

React.renderComponent(
  <HelloBuddies pollInterval={1000}/>,
  document.querySelector('hello-buddies')
);

React.renderComponent(
  <HelloBuddyForm/>,
  document.querySelector('hello-buddy-form')
);

Vous pouvez rafraĂźchir votre page :

Alt "003.png"

Voilà. Nous avons fais un 1er tour rapide. Mais encore une ou deux petites choses 


On fait encore du React, mais avec RequireJS

Cela peut vite devenir le foutoir, quand les composants vont se multiplier. Donc vous aurez certainement envie de structurer tout ça avec RequireJS. Il se trouve qu’il existe un plugin pour pouvoir utiliser JSX : https://github.com/seiffert/require-jsx et ainsi de dĂ©finir les dĂ©pendances (et chargement) de nos composants de cette maniĂšre : 'jsx!components/nom_du_composant'.

Nouvelles dépendances

Donc dans votre petit script loadjs.sh vous pouvez ajouter 2 lignes :

pulldown require.js -o js/vendors
pulldown "https://raw2.github.com/seiffert/require-jsx/master/jsx.js" -o js/vendors

Vous pouvez à nouveau relancer votre script pour récupérer les 2 nouvelles librairies

Ensuite dans votre page index.html, supprimer les inclusion de script et remplacez les par <script data-main="js/main" src="js/vendors/require.min.js"></script>

On refait main.js

Allez ensuite remplacer le contenu de main.js par ceci :

requirejs.config({
  baseUrl : "js/",
  paths   : {
    "jquery"        : "vendors/jquery.min",
    "jsx"           : "vendors/jsx",
    "JSXTransformer": "vendors/JSXTransformer",
    "react"         : "vendors/react.min"
  }
});

require([
  'jsx!application/Application'
], function (Application) {

  Application.initialize()

});

Création de Application.js

Créez un répertoire application dans public/js/ et dans ce répertoire un fichier Application.js :

/** @jsx React.DOM */
define([
    'jquery'
  , 'react'
  , 'jsx!components/HelloTitle'
  , 'jsx!components/HelloBuddies'
  , 'jsx!components/HelloBuddyForm'

], function($, React, HelloTitle, HelloBuddies, HelloBuddyForm){

  var Application = {

    initialize : function() {

      var messageTitle = "Hello World! Hello React!";
      var version = "1.0";

      React.renderComponent(
        <HelloTitle message={messageTitle} version={version}/>,
        document.querySelector('hello-title')
      );

      React.renderComponent(
        <HelloBuddies pollInterval={1000}/>,
        document.querySelector('hello-buddies')
      );

      React.renderComponent(
        <HelloBuddyForm/>,
        document.querySelector('hello-buddy-form')
      );
    }
  }

  return Application;
});

Modifions tous nos composants

C’est assez simple Ă  faire, globalement nous avons juste Ă  les encapsuler dans une dĂ©claration define sans oublier la directive /** @jsx React.DOM */ :

/** @jsx React.DOM */
define(["react","jquery"], function (React, $) {
  var MyComponent = React.createClass({
    // foo ...
  });
  return MyComponent;
});

Nous aurons donc :

# HelloTitle

/** @jsx React.DOM */

define(["react","jquery"], function (React, $) {
  var HelloTitle = React.createClass({

    render: function() {
      return (
        <div>
          <h1>
            {this.props.message}
          </h1>
          <h3>Version {this.props.version}</h3>
          <hr></hr>
        </div>
      );
    }
  });
  return HelloTitle;
});

# HelloBuddies

/** @jsx React.DOM */
define(["react","jquery"], function (React, $) {
  var HelloBuddies = React.createClass({

    getInitialState: function() {
      return {data : [], message : "..."};
    },
    getAllBuddies : function() {

      $.get("buddies", function(buddies) {
        this.setState({data : buddies, message : Date()});
      }.bind(this));

    },
    componentWillMount: function() {
      setInterval(this.getAllBuddies, this.props.pollInterval);
    },

    render: function() {

      var buddyItem = this.state.data.map(function(buddy){
        return (<li>
           {buddy.id} {" "} <strong>{buddy.name}</strong>
        </li>);
      });

      return (
        <div><h2>Buddies List</h2>
          <strong>{this.state.message}</strong>
          <ul className="square">
            {buddyItem}
          </ul>
        </div>
        );
    }
  });
  return HelloBuddies;
});

# HelloBuddyForm

/** @jsx React.DOM */
define(["react","jquery"], function (React, $) {
  var HelloBuddyForm = React.createClass({

    getInitialState: function() {
      return {data : [], message : ""};
    },

    handleSubmit : function() {

      var name = this.refs.name.getDOMNode().value.trim();

      if (!name) {return false;}

      this.setState({
        message : "Please wait ..."
      });

      $.ajax({
        type: "POST",
        url: "buddies",
        data: {name : name},
        dataType: "json",
        success: function(buddy){
          this.setState({
            message : buddy.id + " added!"
          });

          this.refs.name.getDOMNode().value = '';
          this.refs.name.getDOMNode().focus();

        }.bind(this)
      });

      return false;
    },


    render: function() {

      return (
        <div>
          <h2>Buddy Form</h2>
          <form onSubmit={this.handleSubmit}>
            <input type="text" placeholder="name" ref="name"/>

            <input type="submit" value="Add Buddy" />
            <br></br>
            <strong>{this.state.message}</strong>
          </form>
        </div>
        );
    }
  });
  return HelloBuddyForm;
});


 That’s all!

Amusez vous bien! Retours et discussions bienvenus!

PS: ça marche aussi trÚs bien avec Backbone : http://www.thomasboyt.com/2013/12/17/using-reactjs-as-a-backbone-view.html

blog comments powered by Disqus

Related posts