Autoblog de sametmax.com

Ce site n'est pas le site officiel de sametmax.com
C'est un blog automatisé qui réplique les articles de sametmax.com

Réaction à ReactJS 33

Wed, 10 May 2017 18:39:23 +0000 - (source)

Grand utilisateur de jQuery, formateur Angular et maintenant adorateur de VueJS, vous vous demandez sûrement ce que je pense de React. Si, je le sais, mon avis est pour vous comme un phare dans la nuit sombre du frontend engluée dans le brouillard de JavaScript.

Après tout, Facebook est codé avec cette techno pixel par pixel, et ils servent des milliards de pages chaque jour. Ça ne peut pas être mauvais. Ce ne sont pas de cons quand même.

Et puis tout le monde en parle, les asticots gigotent autour des restes du gigot d’ordre que les devs des GAFAS tentent de mettre au menu dans leur régime sans sel et sans typage.

Alors merde, quoi, react, keskeçavo ?

Je vais passer peut être pour un réact (hashtag lol), mais franchement le gigot au brouillard, c’est pas mon plat préféré.

Taillons un peu JS, ça m’avait manqué.

Mon royaume pour un hello world

React est tellement chiant à setuper que la doc officielle vous propose le hello world dans un code pen histoire de pas vous faire fuir tout de suite.

En fait, si pour vous installer un module avec npm/yarn/bower (plutôt que de copier le truc directement ou d’utiliser un CDN) est déjà un truc qui vous fait grogner, vous êtes loin derrière.

Il va vous falloir un outil de build (gulp, grunt ou l’usine à Vespene Webpack) et la série de recettes obsolètes customisées trouvées sur un coin de github pour connecter tous les bouts de votre pipeline. React bien sûr, mais aussi Babel pour transformer le JSX en JS.

Babel suit la left-pad philosophie, à savoir que tout est configurable et éclaté en centaines de petites dépendances et settings. C’est tellement chiant que des packages existent, appelés “presets”, dont l’unique but est de configurer Babel pour une tâche particulière.

Une fois que vous avez tout ça up, vous vous allez à la pêche au tuto, seulement pour remarquer que tout le monde code en ES6, voir ES7, et vous rajoutez donc un peu plus de plugins à tout ce bordel. Allah vous garde si vous tentez d’utiliser TypeScript.

Et vient alors le moment tant attendu pour faire coucou de la main programmatiquement. Ça foire, bien entendu. Le debugger vous pointe sur un bundle.js de plusieurs Mo, et vous apprenez que le support des sources maps est inégal d’un navigateur à l’autre.

Bienvenue.

Le X c’est pas toujours excitant

Une fois passée la douloureuse expérience de mettre en place votre environnement de travail, vous trouvez un certains confort. L’autoreload de la page est franchement pratique, et pouvoir utiliser l’unpacking (pardon le spread), les valeurs de paramètres par défaut et les arrow functions sans vous soucier de la compat du navigateur c’est quand même cool. Avec un webpack tout bien huilé avec amour (et douleur), vous pouvez faire des imports en JS et on a presque l’impression d’utiliser un langage de programmation.

Et puis le JSX, de loin ça à l’air pas mal ! Du HTML directement dans le JS ça parait un peu bizarre mais on vous le vend comme un avantage : finis les langages de templates limités, vous pouvez utiliser la pleine puissance (hum…) du langage JavaScript pour créer vos balises.

Sauf que non, le JSX, ça pue du cul.

D’abord, il y a le fait que toutes les structures sont à faire en JS. Les conditions, les concaténations et bien entendu, les boucles. Donc vous avez une liste de noms et numéros de téléphone, en VueJS, vous faites:

<ul>
    <li v-for="pers in persons" v-if="pers">
        <strong>{{ pers.nom }} </strong>: {{ pers.tel }} 
    </li>
    <li v-else>
        <em>Nobody's here!</em>
    </li>
</ul>

Mais en React, vous allez faire péter une expression ternaire et une fonction anonyme en plein milieu rien que pour l’occasion:

<ul>
    {
      (this.state.persons)
        ? this.state.persons.map((pers) => (
            <li><strong>{ pers.nom } </strong>: { pers.tel } </li>
        ))
        : (
            <li>
                <em>Nobody's here!</em>
            </li>
        )
    }
</ul>

Vous la sentez bien la puissance de JavaScript là ?

Et attention à ces boucles, car dedans vous guette cette erreur qui va vous poursuivre jusque dans vos cauchemars:

SyntaxError: Adjacent JSX elements must be wrapped in an enclosing tag

Babel vous signale gentiment que ne pouvez pas produire un snippet de JSX qui contienne plus d’un élément à la racine. Donc il faut TOUT wrapper dans un container. TOUT. Vous allez avoir vite des DIVs et des SPANs inutiles partout, juste pour faire plaisir à React. C’est absurde. C’est à vomir. Ca nique votre CSS et remplit vos sessions “examinez un élément” de tendinites dues aux clics pour déplier tout l’arbre des emballages cadeaux de vos balises. Et aussi, nique la sémantique.

Pour éviter ça vous allez tenter d’inliner un maximum de trucs dans une seule expression JSX ou tout foutre dans des arrays. Car je ne l’ai pas précisé ? Les arrays de JSX sont automatiquement et magiquement convertis en sous-éléments. Et du coup vous pourrez profiter au maximum des qualités de lisibilité de JS.

JSX est bourré de petits trucs comme ça, pour faire plaisir. Par exemple, j’ai un bouton, quand je le clique il delete la personne de la ligne de mon agenda. Je mets aussi une classe pour tous et une selon que la personne est importante ou non. Un prevent default pour éviter que le browser recharge la page si je suis dans un form.

En Vue, je passe un objet qui dit quelle classe afficher ou non. Le click handler contient une instruction prevent qui me permet d’appeler preventDefault automatiquement. Et j’appelle deletePerson tout naturellement :

<button class="{'delete-person': true, 'important-person': pers.isImportant}" 
        v-on:click.prevent="deletePerson(pers)">
    Delete
</button>

En react, on ne peut pas passer de paramètre à son handler. Il faut le passer dans une closure. Mais alors la closure va recevoir l’objet event, qu’on doit donc faire suivre à notre handler, afin qu’il puisse appeler preventDefault à la main. A noter aussi que class s’appelle className. Et n’accepte que des strings donc la concaténation se fait à la mano.

// plus haut on appelle preventDefault dans handleDelete
 
<button className={'delete-person ' + (pers.isImportant ? 'important-person': '')}
        onClick={(e) => this.deletePerson(pers, e)}>
    Delete
</button>

Ce n’est pas juste chiant à lire, c’est surtout hyper chiant à trouver, à taper et à debugger.

Un peu de nutella sur votre confiture ?

React, c’est verbeux, il va falloir vous y faire. Il suffit de comparer le code nécessaire pour faire une TODO en react avec les autres frameworks.

Mais ce n’est pas juste ça qui est lourd.

Non, le vrai truc qui est super pesant, c’est que react est un cancer. Quand il est utilisé, il contamine tout.

Par principe, une fois que vous utilisez react, il faut mettre TOUTE VOTRE PAGE en react. Pas juste un petit bout. Par là j’entends que 90% de votre HTML va devoir migrer dans le code JSX. Votre beau code HTML bien propre, converti en cette monstruosité de mélange entre le langage le plus dégueulasse du monde et un monstre mimic qui essaye de se faire passer pour du HTML pour vous bouffer.

Or, toute l’idée c’est de faire des composants, de diviser votre pages en plus petits bouts. Mais voilà, React n’a rien prévu de simple pour la communication. Pour passer des données des composants enfants, il faut les passer via les attributs de balise HTML (appelés les props, pour faciliter la rechercher Google). Si vous avez 5 niveaux d’imbrications, vous vous tapez ça 5 fois.

Plus amusant, il n’y a aucun mécanisme pour passer des infos de l’enfant aux parents. La méthode standard est d’écrire puis passer manuellement un callback du parent à l’enfant, via props, et appeler ce callback dans l’enfant avec la valeur que le parent utilise ensuite. POUR. CHAQUE. PUTAIN. DE. VALEUR.

Passer des callbacks, ça va 5 minutes. Et ça ne résout pas un problème de communication entre composants parallèles, c’est à dire ni parent, ni enfant. Du coup tout le monde finit par utiliser une lib supplémentaire type EventEmitter pour servir de bus de communication.

Quand vous entendez les gens se plaindre de la difficulté d’utiliser l’écosystème de react, les fans répondent souvent que l’écosystème n’est pas obligatoire. On peut très bien s’en passer sur un petit projet.

Sauf que react est absolument inutilisable sans. Sans un bus d’event, un transpiler ES6 + JSX, un builder et un hot reloader au mininum, le projet ne dépasse jamais le stade du prototype.

Mais bon soyons franc, beaucoup de projets react tout court ne dépassent jamais le stade du prototype. On parle de codeurs JS là.

Etat

Les props ne doivent jamais changer. Seul l’état d’un composant react peut changer, ce qui trigger un nouveau rendu du composant.

Mais l’état lui-même est en read-only. On est supposé uniquement créer un nouvel état à chaque fois et remplacer l’ancien.

Au début on le fait à la main, mais JS n’est pas vraiment fait pour faire de l’immutabilité, et ça devient uber chiant très vite. Changer la propriété d’un objet qui est lui même dans un array demande de recréer tout l’array et l’objet. A chaque fois.

On se tourne alors vers des libs (encore une) type immutable.js qui fournit des listes et maps qui sont immutables et permettent de faire ces opérations sans y laisser ses jours de congé.

Une fois de plus, les connards qui vous disent qu’on peut faire du react sans la tonne de boiler plate qu’on voit dans les tutos ne le font jamais eux-mêmes. Parce que oui, on peut faire Paris-Amiens à pied en théorie, mais bon…

Des décisions à la con

Clairement, react a été fait pour créer des UI. Mais au bout d’un moment, les utilisateurs se sont réveillés et ont voulu une encapsulation pour des composants non UI. Mais react ne sait pas faire. Quand vous avez donc un composant non UI, par exemple la déclaration de votre routing, vous le déclarez… en JSX:

  render((
      <Router history={browserHistory}>
        <Route path="/foo" component={FooView}/>
        <Route path="/bar" component={BarViw}/>
      </Router>
  ), document.getElementById('app'))

Votre app va devenir très vite une pyramide de composants, certains qui s’affichent, d’autres non, tous se passant en cascade des données les uns aux autres.

Et au passage, quel est l’abruti qui a décidé des noms des méthodes des cycles de vie comme getInitialState, componentWillMount et componentDidMount ?

Surtout que vos méthodes et les méthodes héritées de la classe du component sont dans le même espace de nom.

Le bonheur des stores

Un truc dont on peut vraiment se passer par contre, ce sont les stores. Flux, redux, vuex, etc. On peut utiliser n’importe quelle solution pour stocker l’état de son projet côté client.

Mais si vous voulez profiter des promesses de react, comme le time travel et la concurrence parfaite, il vous faudra un store.

“store” c’est le nom huppé que react donne à ses modèles. Je ne vais pas rentrer dans les détails, et vous laissez décider par vous même du truc. Voici, comment la doc vous recommande d’ajouter une personne dans une liste d’un agenda avec redux, la store la plus populaire:

// Les imports
import React from 'react';
import { render } from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
 
// creation du store
const initialState = {'agenda': []};
 
function reducer(state=initialState, action){
  switch (action.type) {
    case 'ADD_PERSON':
      return {
        'agenda': [..., action.object]
      }
    default:
      return state
  }
}
 
const store = createStore(reducer);
 
 
// creation de l'action d'ajouter un objet dans le store
 
function addPersonAction(obj){
    return {
        'type': 'ADD_Person',
        'object': obj
    };
}
 
 
export function addPerson(Person){
    store.dispatch(addPersonAction(Person));
}
 
 
// composant qui affiche le form de l'agenda et la liste des personnes
var Agenda = React.createClass({
 
  handleAdd: function(){
      addPerson({
        "name": this.refs.name.value,
        "tel": this.refs.tel.value
      })
  },
 
  render: function(){
 
    return <form onClick={this.handleAdd}>
        <p><input ref="name" /><input ref="tel" /><button>Add</button></p>
    </form>
 
    <ul>
        {
            this.store.agenda.map((pers) => (
                <li><strong>{ pers.nom } </strong>: { pers.tel } </li>
            ))
        }
    </ul>
  }
 
 
});
 
 
// adapter qui permet de passer le store à l'agenda
const AgendaContainer = connect(function(state){
    return {'agenda': state.get("agenda")};
})(Agenda);
 
 
// affichage du bouzin
render((
<Provider store={store}>
  <AgendaContainer>
</Provider>
), document.getElementById('app'))

Vous imaginez bien que trouver ça tout seul a été un bonheur. Parce que oui, la doc est absolument à chier, dans la longue tradition des stacks JS modernes.


Powered by VroumVroumBlog 0.1.32 - RSS Feed
Download config articles