JavaScript, à l’ancienne ? typé avec TypeScript ? Flow ?

UNE ASSISTANCE TECHNIQUE ? NOUS SOMMES DISPONIBLES UNE ASSISTANCE TECHNIQUE ? NOUS SOMMES DISPONIBLES

Accéder au sommaire de “Guide pratique du développeur React Native”

Il existe une multitude de possibilités pour concevoir une application JavaScript. Le langage JavaScript à la mode Netscape des années 90 est révolu, 25 ans plus tard, JavaScript a subit des évolutions successives pilotées par l’ECMA. Utilisé d’abord sur le poste client à travers un navigateur, il a ensuite été déporté sur le serveur grâce (entre autre) à NodeJS et au moteur V8 de Chrome.  On compte aujourd’hui une trentaine de moteurs JS à travers le monde.  Plus que jamais JavaScript caracole en tête des langages les plus utilisés et son écosystème ne cesse de croître, dynamisé par les nombreux projets sur GitHub.

Nous ne détaillerons pas ici les relations entre React Native et son moteur JS, j’ai déjà eu l’occasion d’écrire un article plus technique sur le sujet.  Mais pour synthétiser très sommairement, React Native est constitué à 90% de code JavaScript et à 10% de briques techniques interagissant avec les widgets natives à l’aide d’un pont de communication bidirectionnel.

Le choix du langage est primordial dans un projet. Il existe en effet plusieurs JavaScript : ES6, ES5, ES6, ES9, ES10 mais aussi du JavaScript compilé avec TypeScript, Flow ou CofeeScript. Le choix de votre JavaScript dépendra également des dépendances avec d’éventuels modules externes. Mais React Native ne vous impose rien, si vous générez votre application avec un générateur tel que CRNA il vous suffira de préciser vos préférences de configuration pour obtenir un squelette de projet pré-configuré. A titre d’exemple, voici un squelette créé pour fonctionner avec TypeScript en version 3.8.3, les 2 informations principales sont contenues dans le fichier tsconfig.json et l’ajout de la dépendance typescript dans le fichier package.json.

Mais quelle est l’incidence du choix d’une version de JavaScript ?

L’incidence est multiple. A partir du moment où le choix du langage est entériné, il faudra sensibiliser les développeurs à la qualité du code. De nombreuses fonctionnalités dépendent de la version utilisée : les décorateurs, certains opérateurs, les promesses, les imports de modules, … Les linters sont des outils qui vont contribuer à structurer le code et agir comme des pseudo-compilateurs en phase statique. Le plus connu est ESLint, mais on trouve aussi JSLint, TSLint, JSHint, JSCS, … Il existe autant de Linters que de combinaisons possibles de configurations JavaScript. Ces articles (https://blog.sodifrance.fr/les-linters-javascript/ et https://www.sitepoint.com/comparison-javascript-linting-tools/) vous aideront à comprendre les avantages et inconvénients de chaque linter. Sachez qu’une immense majorité des développeurs ont une préférence pour ESLint lorsqu’il s’agit de JavaScript.

Typé ou non Typé ?   

C’est une question fondamentale. La question aurait été posée il y a encore 2 ou 3 ans une grande partie de la communauté JavaScript aurait crié au loup car JavaScript est par essence un langage non typé. JavaScript s’est différencié de Java (ou C#) justement pour ses qualités de langage interprété (par opposition aux langages compilés) et dynamique (inférence de type, typage au runtime, …). Parler de typage à l’époque aurait été plutôt incongru. Aujourd’hui, excepté quelques purs et durs qui voient là un dévoiement total des origines de JS, plus personne ne croit réellement aux vertus du JS à la mode Netscape des années 95. Plus une application devient importante, plus elle nécessite que les erreurs soient détectées tôt. La compilation et le typage jouent un rôle important dans la détection de ces erreurs en phase de codage.

Il y a un exemple que je cite souvent s’agissant du typage. Il suffit d’observer les nombreux Framework JavaScript réalisés il y a 5 ans et d’observer comment ils ont évolué au fil du temps. L’exemple le plus criant est sans doute celui de React et React Native. Ces deux Framework initialement non typés ont subi en cours de route des évolutions majeures, initialement non typé, Facebook a dû réécrire des pans entiers de React pour y intégrer son compilateur maison : Flow. Dès 2016 les premiers travaux autour de cette réécriture étaient initiés. Ce typage a continué et comme le montre la classe ReactContext, on y voit clairement l’utilisation du typage contenu dans le fichier ReactTypes.js.

Les meilleurs expert JS s’accordent aujourd’hui à dire que le typage est indispensable, notamment lorsqu’on propose une bibliothèque ou un composant.

Mais l’exemple précédent n’est pas anodin, Flow est le compilateur retenu par Facebook. Est-ce la panacée ? Quelle est la différence entre Flow et les autres ? TypeScript n’est-il pas un meilleur candidat ?

Flow ou TypeScript ? Un choix vraiment cartésien ?

J’ai eu la chance de participer à des projets avec Flow et TypeScript. Durant l’un de mes tous premiers projets React Native typés, je pensais (à tord) que si Facebook avait fait le choix de Flow, il semblait somme toute logique de garder une cohérence en choisissant Flow pour une application mobile s’appuyant sur React Native.  C’était une erreur. A aucun moment le fait d’utiliser Flow n’a facilité notre compréhension du code interne de React, bien au contraire.

Flow reste une énigme encore pour bons nombre de développeurs JS. Alors que Microsoft a réussi à imposer TypeScript sur le marché et que Flow n’a jamais réellement pu dépasser les frontières de React, on se demande encore pourquoi Facebook s’obstine à pousser cette brique.

D’un point de vue technique, TypeScript est beaucoup plus complet que Flow. Ce qui est somme toute normal, Flow n’est pas réellement un langage, c’est avant tout un analyseur statique de type :

Flow is NOT a programming language in itself; but rather, a “Static Type Checker for JavaScript”. What this means is that Flow is a productivity tool that you can download and install into your local environment, which in return will analyse your code to generate insightful information.

Dans la pratique, mon expérience personnelle avec Flow n’a pas été réellement concluante. Pas pour des raisons techniques liées à Flow mais plutôt pour la pauvreté de son écosystème. On trouve aujourd’hui très peu d’outils compatibles avec Flow, que ce soient les plugins sous IntelliJ, VSCode (lent, lourds, souvent buggés, …) ou même les générateurs de code. Des outils largement plébiscités comme Swagger CodeGen en font carrément l’impasse, il suffit de voir le nombre de langages supportés par Swagger (https://github.com/swagger-api/swagger-codegen) et le peu d’intérêt porté pour Flow. Dans ce ticket, une PR a été soumise, mais 3 ans après, toujours aucune volonté de l’intégrer.

Toutes ces raisons m’ont finalement convaincues que Flow n’était pas une option viable sauf à réaliser une extension ou un module React.

TypeScript, un bon langage ? 

Faites-vous votre opinion sur le sujet, nous proposons une formation dont le support est mis à disposition gratuitement sur TypeScript. C’est le compilateur le plus abouti s’agissant du typage JavaScript. Dans la pratique, développer avec TypeScript sans un minimum de connaissance peut vite tourner au calvaire. Pour ceux ayant des compétences OOP (Java ou C#,) attendez-vous à souffrir un peu car les différences de paradigme entre Java et TypeScript sont important, il ne faut pas se voiler la face. L’intersection, l’union des types, l’utilisation des types undefined et null, la détructuration, etc … Toutes ces notions n’existent pas dans les langages objets compilés. JavaScript est un langage par essence dynamique qui n’a pas été prévu pour être tordu de cette manière, on arrive parfois à une verbosité assez caractéristique de TypeScript. Celui ci en est un exemple, c’est un extrait du typage React en TS,

// naked 'any' type in a conditional type will short circuit and union both the then/else branches
// so boolean is only resolved for T = any
type IsExactlyAny<T> = boolean extends (T extends never ? true : false) ? true : false;

type ExactlyAnyPropertyKeys<T> = { [K in keyof T]: IsExactlyAny<T[K]> extends true ? K : never }[keyof T];
type NotExactlyAnyPropertyKeys<T> = Exclude<keyof T, ExactlyAnyPropertyKeys<T>>;

// Try to resolve ill-defined props like for JS users: props can be any, or sometimes objects with properties of type any
type MergePropTypes<P, T> =
    // Distribute over P in case it is a union type
    P extends any
        // If props is type any, use propTypes definitions
        ? IsExactlyAny<P> extends true ? T :
            // If declared props have indexed properties, ignore inferred props entirely as keyof gets widened
            string extends keyof P ? P :
                // Prefer declared types which are not exactly any
                & Pick<P, NotExactlyAnyPropertyKeys<P>>
                // For props which are exactly any, use the type inferred from propTypes if present
                & Pick<T, Exclude<keyof T, NotExactlyAnyPropertyKeys<P>>>
                // Keep leftover props not specified in propTypes
                & Pick<P, Exclude<keyof P, keyof T>>
        : never;

Le modèle d’expression de TypeScript peut parfois mener à des aberrations comme on peut le voir avec le typage conditionnel qu’on ne retrouve pas sous cette forme dans d’autres langages statiquement typés. Il vous faudra donc trouver un juste milieu pour simplifier autant que possible votre typage.

Malgré ces points inhérents à la complexité de TypeScript sur des cas de typage bien particuliers, il me paraît difficile aujourd’hui de développer une application React Native sans un minimum de typage. Notamment si on utilise des outils tels que GraphQL, TypeOrm (pour le modèle de données) ou des composants graphiques tels que Material UI ou Bootstrap.

Ah, et les Hooks …

Ces dernières années ont été douloureuses pour un grand nombre de développeurs React. Si vous ne connaissez pas les Hooks, cet article devrait vous aider.

Les Hooks sont un changement radical de paradigme dans la manière d’appréhender la gestion de l’état local d’un composant. Il y a un avant et un après les Hooks. Cette évolution majeure a non seulement modifié notre manière de coder avec React mais aussi celles des dépendances et modules que nous utilisons au quotidien : les composants graphiques,  la navigation, les appels de services, le stockage de données local, etc … Tout l’écosystème React a dû se renouveler pour assurer une compatibilité avec les Hooks. Certains composants comme react-native-calendar ont même failli abandonner.

Il est primordial de s’assurer au démarrage d’un projet de la compatibilité de toutes les dépendances avec les Hooks. Cela signifie probablement travailler avec des versions récentes, voire en béta. A titre d’exemple, j’ai du développer Dossardeur avec React Navigation 4 qui ne proposait les Hooks qu’avec une extension “temporaire” : react-navigation-hooks. Maintenant que la version 5 (Hookisée) est disponible, une migration va être nécessaire. Or, mettre à jour l’outil de navigation n’est jamais une opération anodine tant les impacts sont nombreux.

Voilà encore un piège classique dans lequel on peut facilement tomber lorsqu’on aborde le monde React sans un minimum  de connaissances.

Bibliographie

Le guide de référence TypeScript & React : Indispensable pour comprendre les subtilités (nombreuses) du typage dans React

Typescript vs Flow : Les avantages et inconvénients de chaque outil

TypeScript vs Flow : Un autre article sur les différences entre TypeScript et Flow

UNE ASSISTANCE TECHNIQUE ? NOUS SOMMES DISPONIBLES UNE ASSISTANCE TECHNIQUE ? NOUS SOMMES DISPONIBLES