ReactJS

Facebook ReactJS – Quelques ressources pour bien commencer

Le framework frontend du moment est sans conteste AngularJS. Le nombre d’articles et tutoriaux qui lui sont dédiés, les statistiques de recherche Google associées, les likes et contributeurs sur Github ou encore le nombre de questions sur Stackoverflow sont autant de métriques allant dans ce sens.

Jusqu’à aujourd’hui, les développeurs avaient souvent tendance à comparer les points forts et points faibles des frameworks frontend suivants avant de se lancer dans un projet :

Mais depuis plusieurs mois, un nouveau venu fait beaucoup parler de lui : React.js. Cette librairie a été open-sourcée par Facebook mi-2013 et est le fruit d’un travail commun entre les équipes de Facebook et d’Instagram.
Facebook présente React comme une librairie Javascript pour construire des interfaces utilisateur.

React : A Javascript library for building user interfaces

ReactJS présente quelques atouts extrêmement intéressants dont son Virtual DOM qui permet notamment :

  • Des performances de rendering bluffantes
  • L’Isomorphisme. Les composants React peuvent être rendus simplement côté serveur sans PhantomJS. SEO friendly, perfs au premier chargement…

D’autre part, son approche orientée composant force le développeur à architecturer son application en petits éléments autonomes et imbriquables dans des composants de plus haut niveau. Un peu à la manière des directives Angular, des components Ember ou des futurs Web Components (lire à ce sujet, React VS Web Components), sauf qu’avec React, tout est composant.

Au sein de cet article, j’ai tenté de regrouper les ressources les plus intéressantes que j’ai pu trouver sur le net et qui m’ont permis de comprendre un peu mieux le fonctionnement de React, en quoi son approche diffère des frameworks existants tels que Angular ou Ember, qui utilise cette librairie aujourd’hui et quel peut-être l’intérêt de l’intégrer au sein d’un projet existant ou d’un nouveau.

Si vous avez connaissance d’un article ou d’un tuto qui ne figure pas dans la liste ci-dessous, je suis preneur ;-)

ReactJS vs le reste du monde – React, c’est quoi, pourquoi ?

React – Qu’est-ce que c’est ?

Is React a Template Library? Is React Similar to Web Components? Are the Virtual DOM and Shadow DOM the Same? Autant de questions qu’on peut se poser quand on commence à s’intéresser à React et auxquelles l’auteur de cet article très complet tente de répondre :

http://www.funnyant.com/reactjs-what-is-it/

6 raisons d’aimer React

http://www.syncano.com/reactjs-reasons-why-part-1/

Angular vs React.js pour des applications web complexes

http://blog.liip.ch/archive/2014/09/16/angularjs-vs-reactjs-for-large-web-applications.html

Facebook React vs Web components

http://programmers.stackexchange.com/questions/225400/pros-and-cons-of-facebooks-react-vs-web-components-polymer

Tutos et exemples de code

Facebook’s React.js

La documentation sur le site officiel est très riche et très complète. Le quickstart notamment est un passage obligé pour comprendre les concepts clés de React :

http://facebook.github.io/react/docs/getting-started.html

Intégration de React à Angular

Un article très intéressant proposant une solution pour améliorer la performance de rendu de longues listes dans Angular en utilisant ReactJS :

http://www.mono-software.com/blog/post/Mono/242/Improving-AngularJS-long-list-rendering-performance-using-ReactJS/

Un autre exemple très détaillé traitant du même sujet :

http://www.williambrownstreet.net/blog/2014/04/faster-angularjs-rendering-angularjs-and-reactjs/

Starter Kit

Un starter kit très complet intégrant React à l’architecture Flux de Facebook :

https://github.com/kriasoft/react-starter-kit

Divers

Qu’est-ce qu’une isomorphic webapp et comment React facilite l’implémentation de ce type d’application :

http://bensmithett.github.io/going-isomorphic-with-react/

Components, plugins, librairies

La quantité de composants ou plugins React est actuellement très inférieure comparée au nombre de directives Angular open-sourcées par la communauté, mais il existe déjà un bel annuaire qui les recense et facilite leur recherche :

http://react-components.com/

En bonus, l’application est open-source et implémentée avec React.

React Router

L’un des projets autour de React les plus populaires :

https://github.com/rackt/react-router

OM

Un framework ClosureScript construit autour de React :

https://github.com/swannodette/om

React.js, qui l’utilise ?

On peut légitimement se demander si React est prêt à être utilisé ou si il manque aujourd’hui beaucoup trop de maturité. Les grands noms du Web ont en tous cas l’air de se pencher très sérieusement sur cette librairie.

Feedly

Github

Github a choisi React pour résoudre un certains nombre de soucis de performances rencontrés lors de l’implémentation de leur éditeur Atom :

http://blog.atom.io/2014/07/02/moving-atom-to-react.html

Adobe

Adobe intègre ReactJS au sein de son éditeur brackets notamment pour le rendu du file tree.

http://www.kevindangoor.com/2014/05/react-in-brackets/

Divers

http://wiredcraft.com/posts/2014/08/20/why-we-may-ditch-angularjs-for-react.html

Un tour d’horizon des projets utilisant officiellement React :

http://facebook.github.io/react/blog/2014/10/17/community-roundup-23.html

Architecture d’une application avec React : Flux, Reflux…

React se présente comme étant le V du pattern MVC. On peut facilement l’intégrer au sein de frameworks complets comme Angular ou Backbone mais Facebook a présenté la façon dont ils utilisent React au sein d’une application complexe en documentant l’architecture de leur projet appelée Flux.

http://facebook.github.io/flux/

Yahoo Flux

Yahoo a adopté Flux et maintient plusieurs projets implémentants les différents composants de l’architecture Flux.

Yahoo a également publié un dépôt Github avec un exemple d’application utilisant Flux et React.

https://github.com/yahoo/flux-examples

Reflux

Reflux est une alternative à l’architecture Flux tentant de simplifier son approche tout en conservant le concept de flow de données unidirectionnel :

The goal of the refluxjs project is to get this architecture easily up and running in your web application, both client-side or server-side.

Le projet sur github : https://github.com/spoike/refluxjs

http://spoike.ghost.io/deconstructing-reactjss-flux/

600px-JavaScript-logo

Array.forEach n’est pas toujours la meilleure solution !

EDIT : Je vous invite à lire les retours très pertinents et intéressants de @naholyr, @BAKfr et @KoonePoew suite auxquels j’ai modifié les exemples et une partie du contenu de cet article.

Ajoutée sur ECMAScript 5 (ES5) aux côtés de nombreuses autres fonctions tableaux, Array.prototype.forEach permet de parcourir des tableaux javascript de manière moins verbeuse (plus moderne ?) qu’avec la classique boucle for.

var array = [1,2,3,4,5];
for (var i = 0 ; i < array.length ; i++) {
  console.log(array[i]);
}

Le bout de code ci-dessus peut alors être écrit de la manière suivante :

var array = [1,2,3,4,5];
array.forEach(function(value) {
  console.log(value);
});

Il faut bien reconnaître que c’est quand même plus sexy ! Je passe volontairement sur les subtilités de l’utilisation d’un callback et sur le contexte de this dans ce dernier pour m’attarder plutôt sur les quelques exemples de code qui vont suivre.

Je privilégie depuis longtemps l’utilisation du forEach ES5 en lieu et place du classique for et j’encourage également à se passer le plus possible des pseudo forEach implémentés au sein des frameworks tels que AngularJs (angular.forEach permet par exemple un parcours à la fois de tableaux classiques et de propriétés d’objets ~ tableaux associatifs…) en utilisant au besoin un script tel que es5-shim si le support des vieux navigateurs est obligatoire.

Mais si on applique bêtement cette règle, on en vient parfois à utiliser forEach en dépit du bon sens…

function isElementVisible(element) {

    var isVisible = false;
    element.children.forEach(function(child) {
        if (child.visible) {
            isVisible = true;
            // Ici, on continue alors qu'on a déjà trouvé notre réponse...
        }
    });

    return isVisible;
}

var plop = //...
if (!isElementVisible(plop)) {
    // ...
}

Beaucoup de développeurs habitués à jQuery et à son jQuery.each() avant de développer en « vrai » Javascript pensent qu’il suffit de faire un return false dans le callback de l’appel à Array.prototype.forEach pour breaker le parcours. Or, il n’y a en fait aucun moyen de stopper ce parcours. Partant de ce postulat, le bout de code ci-dessus a perdu en lisibilité, en performance et donc en intérêt.

Comme pointé par @naholyr, @BAKfr et @KoonePoew dans les commentaires, c’est en fait Array.prototype.some qui répond parfaitement à notre besoin. Comme expliqué sur le site de Mozilla, la méthode some() teste si certains éléments du tableau passent le test implémenté par la fonction fournie.

Notre code peut alors être très simplement réécrit de cette manière :

function isElementVisible(element) {

    return element.children.some(function(child) {
        return child.visible;
    });
}

Notez que some s’arrête dès que le callback renvoie true, ce qui est exactement ce que l’on recherche.

Prenons maintenant l’exemple de la recherche d’un élément par son code :

function findElementByCode(elements, code) {

    var searchedElement = null;
    elements.forEach(function(element) {
        if (element.code === code) {
            searchedElement = element;
        }
    });

    return searchedElement;
}

Là encore, pas moyen de breaker au sein du forEach() ce qui peut poser des problèmes sérieux de performance.

Pour améliorer la performance de cette fonction, il est possible de la modifier pour utiliser l’alternative Array.prototype.some qui, elle, autorise bien le break dans le callback :

function findElementByCode(elements, code) {

    var searchedElement = null;
    elements.some(function(element) {
        if (element.code === code) {
            searchedElement = element;
            return true;
        }
    });

    return searchedElement;
}

Cette fois, les performances sont de retour mais on perd encore un peu plus en lisibilité. On a ajouté un return pour breaker (on ne retourne pas la valeur recherchée) et on détourne l’utilisation de la méthode de test some.

ECMAScript 6 ajoute une fonction find() sur le prototype de Array qui permettra de réécrire à terme cette recherche de la manière suivante :

function findElementByCode(elements, code) {

    return elements.find(function(element) {
        return element.code === code;
    });
}

Et si finalement, en attendant la sortie et l’adoption d’ES6, la bonne vieille boucle for n’était tout simplement pas LA solution :

function findElementByCode(elements, code) {

    for (var i = 0 ; i < elements.length ; i ++) {
        if (elements[i].code === code) {
            return elements[i];
        }
    }

    return null;
}

Certains pourraient être tentés de mettre en cache la propriété length dans cette fonction pour des raisons de performances :

function findElementByCode(elements, code) {

    var elementsLength = elements.length;
    for (var i = 0 ; i < elementsLength ; i ++) {
        if (elements[i].code === code) {
            return elements[i];
        }
    }

    return null;
}

Attention à ce type de micro-optimisation qui n’a plus de sens dans les navigateurs modernes où la mise en cache de length est faite automatiquement à l’exécution. Comme souvent, il y a certainement des gains de performances bien plus important ailleurs avant qu’il ne soit nécessaire de complexifier le code source pour ce type de pseudo-optimisation (Voici le résultat d’un test intéressant sur jsperf pour vous convaincre : javascript length cache vs no cache).

Finalement, si je devais proposer une bonne pratique ce serait la suivante :

  • Utiliser le forEach natif ES5 pour les parcours intégraux de tableaux
  • Rechercher « s’il existe une méthode avec la sémantique que l’on cherche » (cf commentaire de @BAKfr)
  • Utiliser for (var i ; ...) dans les autres cas
AngularJS

Accéder à un service AngularJs depuis la console

Quand on débogue une application Angular il est bien pratique de pouvoir accéder et utiliser ses services depuis la console.
Si vous n’avez pas violemment injecté tous vos modules et services dans des variables globales, il n’est pas évident au premier abord de les récupérer.

Le bout de code suivant vous permet de récupérer l’instance du DI et donc de récupérer les services de notre application :

// récupération de l'injecteur de dépendances
// dans cet exemple, élément root (`ng-app`) affecté au noeud `html`.
var injector = angular.element("html").injector();

Une fois l’instance du DI récupérée, on peut utiliser la méthode get(...) associée :

// récupération du service GouzigouzaService
// dans cet exemple, élément root (`ng-app`) affecté au noeud `html`.
var monService = angular.element("html").injector().get('GouzigouzaService');

monService.plop(); // exécuter `plop`
monService.value; // accéder à la valeur `value`
600px-JavaScript-logo

Utilisez la méthode Function.prototype.bind() de Javascript

Chaque développeur javascript a un jour ou l’autre rencontré un problème avec le contexte associé à this.

Prenons l’exemple de code simpliste suivant :

var myObject = {

  crazyMessage: 'gouzigouza',

  doSomethingCrazy: function() {
    alert(this.crazyMessage);
  }
};

myObject.doSomethingCrazy();

Mon objet contient une propriété crazyMessage et une méthode doSomethingCrazy qui utilise la propriété crazyMessage.
Jusqu’ici tout va bien, le contexte de this au sein de la méthode doSomethingCrazy est bien l’objet myObject.

Maintenant, dans un élan de folie, je veux implémenter la méthode doSomeAsyncCrazyness :

var myObject = {

  crazyMessage: 'gouzigouza',

  doSomethingCrazy: function() {
    alert(this.crazyMessage);
  },

  doSomeAsyncCrazyness: function() {
    setTimeout(function() {
      this.doSomethingCrazy();
    }, 1000);
  }
};

myObject.doSomeAsyncCrazyness();

Aussi surprenant que cela puisse paraître, je récupère une erreur dans ma console :

Uncaught TypeError: undefined is not a function 

Cette erreur est due à this qui est désormais associé au contexte global et non plus à mon objet myObject, à cause de l’appel à setTimeout.

Mettre this en « cache »

Pour pallier ce problème, l’approche très souvent adoptée par le développeur est d’assigner this à une variable intermédiaire temporaire de type that ou self de la manière suivante :

var myObject = {

  crazyMessage: 'gouzigouza',

  doSomethingCrazy: function() {
    alert(this.crazyMessage);
  },

  doSomeAsyncCrazyness: function() {

    var that = this;
    setTimeout(function() {
      that.doSomethingCrazy();
    }, 1000);
  }
};

myObject.doSomeAsyncCrazyness();

De cette manière, l’appel à la méthode doSomeAsyncCrazyness fonctionne en effet comme prévu.

Une méthode bien plus élégante existe cependant et ce depuis ECMAScript 5.

.bind() magic

La méthode .bind() disponible sur le prototype de Function résoud notre problème en permettant, entre autres choses, de définir explicitement le contexte associé à this.

Nous pouvons donc réécrire notre code précédent de la manière suivante :

var myObject = {

  crazyMessage: 'gouzigouza',

  doSomethingCrazy: function() {
    alert(this.crazyMessage);
  },

  doSomeAsyncCrazyness: function() {
    setTimeout(function() {
      this.doSomethingCrazy();
    }.bind(this), 1000);
  }
};

myObject.doSomeAsyncCrazyness();

Avouez que c’est quand même plus joli.

Support des vieux navigateurs

Et si mon code doit s’exécuter dans un navigateur qui ne supporte pas ES5 ? Internet Explorer < 9 pour ne pas le nommer.
Dans ce cas, plusieurs solutions.

  • Continuer avec la technique de mise en cache de this
  • Utiliser la méthode .bind implémentée dans la plupart des frameworks JS (Angular.bind() par exemple)
  • Implémenter un fallback quand la méthode n’est pas disponible

Mozilla notamment propose le fallback suivant (Mozilla polyfill Function.prototype.bind()) :

if (!Function.prototype.bind) {
  Function.prototype.bind = function (oThis) {
    if (typeof this !== "function") {
      // closest thing possible to the ECMAScript 5
      // internal IsCallable function
      throw new TypeError("Function.prototype.bind - 
           what is trying to be bound is not callable");
    }

    var aArgs = Array.prototype.slice.call(arguments, 1), 
        fToBind = this, 
        fNOP = function () {},
        fBound = function () {
          return fToBind.apply(this instanceof fNOP && oThis
                 ? this
                 : oThis,
                 aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();

    return fBound;
  };
}

Pour aller plus loin

Quelques ressources pour aller plus loin :