Dans une application développée en React Native, le code JavaScript est exécuté par un moteur à l'intérieur du téléphone. Pour débogger une application, l'approche historique était d'activer le mode debug de React Native. Ce mode permet de déporter l'exécution du code Javascript du téléphone vers un debugger (comme React Native Debugger ou l'inspecteur JS sur Chrome) afin de prendre le contrôle du code exécuté.
Cela présente deux inconvénients majeurs : les mêmes problèmes ne sont pas toujours reproduits avec ou sans le mode debug et l'exécution est fortement ralentie.
Flipper est un outil d’inspection et de débogage d’application mobile créé par Facebook. Il peut être utilisé pour du React Native ou pour des applications natives. Chez BAM, nous l’employons majoritairement sur nos applications React Native.
Avec Flipper et Hermès (un moteur javascript optimisé pour React Native), le debugging s’opère directement au niveau du téléphone. Cela rend l’opération plus rapide et surtout plus fiable. Flipper utilisant Hermès pour son debugger, l’activation d’Hermès est obligatoire sur l'application à déboguer.
Au-delà du debugging JavaScript, Flipper présente d'autres avantages. L'outil propose différents plugins d’inspection de réseau, de layout et de mémoire. Il peut se brancher à des librairies comme react navigation ou react query, qui possèdent un plugin Flipper.
De plus, Flipper étant très extensible, il est facile d’ajouter des plugins faits maison. Cela permet notamment d'avoir des mesures spécifiques à l’application développée, ou encore des interactions spéciales pouvant aider les développeurs. A titre d’exemple, on peut imaginer un plugin pour activer/désactiver quelque chose dans l’application. Chez BAM, nous avons notamment développé un plugin pour inspecter la performance de nos applications.
Nous recommandons aujourd'hui Flipper comme debugger par défaut sur nos projets React Native. Si son installation était anciennement compliquée, en raison de dépendances natives, ce n'est plus le cas aujourd'hui. Flipper répond bien mieux à nos besoins d'outillage que React Native debugger.
Le temps de démarrage des apps a longtemps été un point noir de React Native, surtout sur des téléphones Android bas de gamme. A titre d’exemple, l’une de nos apps, qui contenait un code JS conséquent, démarrait en 12.9s sur un téléphone bas de gamme.
Pour remédier à ce problème, Facebook a implémenté Hermes, un nouveau moteur JavaScript. Au lieu de parser et compiler le JS en code binaire au lancement de l'app (comme un moteur JS traditionnel), Hermes le fait pendant le "build time" de l'app.
Ainsi, une app sous Hermes n'a plus de fichier JS embarqué, mais un fichier de code binaire. Les résultats sont frappants : en utilisant Hermes, l'app mentionnée précédemment démarre maintenant en 3.9s, et le temps de démarrage de toutes nos apps a été divisé au moins par 2. Passer enableHermes à true dans la configuration et ajouter quelques polyfills (ex: i18n, regexp) vous permettront de profiter de cette amélioration.
Chez BAM, nous activons Hermes sur tous nos projets et nous vous recommandons de faire de même. Les bénéfices surpassent les coûts, car sans Hermes, le temps de démarrage de vos apps Android ne sera probablement pas à la hauteur des recommandations de Google.
Si de nombreux développeurs sous-estiment la complexité associée aux appels asynchrones, ces derniers constituent en réalité un problème très vaste et complexe (gestion de l'état de chargement, de l'état d'erreur, du cache, du refetch). Une implémentation simple et performante peut s’avérer très complexe.
React Query est présentée comme une librairie de hooks pour charger, mettre en cache et modifier des données asynchrones en React. Elle offre un large éventail de fonctionnalités : une simple requête avec un état d'erreur et de chargement, des requêtes dépendantes ou en parallèle, des requêtes paginées. Elle intègre également une gestion simple et efficace du cache et assure une compatibilité avec du rendu côté serveur et notamment NextJS.
En revanche, le choix a été fait de ne pas normaliser le cache. Cette action doit donc être réalisée manuellement si nécessaire.
Chez BAM, cela fait bientôt deux ans que nous avons commencé à utiliser React Query. Cette librairie est très simple à prendre en main. Nous vous conseillons tout de même de porter attention à la configuration par défaut, car la durée du cache et le nombre de tentatives des requêtes en cas d'erreur peuvent varier selon les projets. Nous avons constaté que cette solution nous a permis d'implémenter beaucoup plus rapidement nos appels asynchrones, avec une meilleure qualité, ce qui nous a permis de concentrer nos efforts sur des fonctionnalités métier. Nous l'utilisons maintenant sur tous nos projets qui n'ont pas d'API graphql, et nous vous conseillons de faire de même.
L'univers mobile n'est pas toujours évident à prendre en main pour des développeurs qui n'ont pas d'expérience dans le domaine. La nécessité de comprendre les spécificités de chaque OS, leurs APIs et la manière de signer et déployer les applications peuvent constituer une véritable barrière à l'entrée.
Expo est une plateforme qui permet de développer des applications React Native en atténuant grandement cette barrière. Elle permet de générer une application en "managed workflow" qui abstrait toute la partie native et dont il est possible de s'éjecter (à la manière de create-react-app). Cette plateforme présente de nombreuses fonctionnalités, qui simplifient le quotidien des développeurs, comme :
Nous avons eu une bonne expérience avec cette plateforme, utilisée sur plusieurs de nos projets en production. Un point de vigilance cependant : en utilisant Expo, on devient très dépendant du service. Il nous est arrivé de ne pas pouvoir déployer notre application pendant plusieurs heures, car cette plateforme était indisponible. De plus, il est maintenant possible de modifier le code natif même en "managed workflow" via des patchs moyennement faciles à maintenir.
Ce n’est donc pas une solution idéale pour des projets qui requièrent beaucoup de modifications du code natif. Nous conseillons plutôt Expo aux équipes qui travaillent sur un projet de petite à moyenne taille et qui n’ont pas ou peu de connaissances dans le domaine du mobile.
React Native est un framework basé sur React, qui permet d'implémenter des applications natives iOS et android à partir d'une seule base de code. Pour ce faire, deux pans techniques sont nécessaires :
En informatique, il existe deux grandes stratégies pour faire communiquer deux pans techniques :
Pendant des années, React Native a utilisé des messages JSON sérialisés, envoyés à travers un bridge pour faire communiquer les pans techniques JS et natif.
Ce système a ses limites, car lorsque le débit d'information en transit entre les deux pans techniques est trop important (animations, évènements de scroll sur de longues listes...), le bridge se congestionne. On constate alors des problèmes de réactivité de l'application.
Pour pallier ces problématiques, la nouvelle architecture React Native dispose d'une brique appelée JSI, une Foreign Function Interface qui relie le runtime JS au domaine natif. Cette interface standardisée est déjà implémentée dans certains moteurs JavaScript comme JSC et Hermes. Son utilisation permet d’améliorer de manière significative la performance. Certaines librairies ont déjà tiré profit de JSI :
Cependant, JSI est également source de complexité, car la liaison entre le pan technique natif et le pan technique JS de votre module devra être implémentée en C++. De plus, la documentation est encore peu fournie et le développement d’API asynchrones est complexe, ce qui peut freiner son déploiement à grande échelle.
Si le module que vous souhaitez implémenter possède des interfaces simples (manipulant des types sérialisables), des outils comme codegen permettent de générer le code C++ lié à JSI, à partir d'une interface de votre module écrite en Typescript ou en Flow.
Sinon il vous faudra créer vos fichiers de liaison en C++, utiliser les API de JSI et créer vos propres hostObjects pour pouvoir implémenter vos modules natifs.
Aujourd'hui, nous expérimentons la migration de notre module le plus connu, react-native-image-resizer, vers JSI, et nous vous conseillons de l’étudier pour vos modules natifs.
Une application étant souvent disponible à la fois sur le web et sur le mobile, il est naturel de chercher à partager du code entre ces plateformes. Il est possible de partager de la logique métier, du state d'UI et des call API entre une application web React et une application mobile React Native, mais il n'est pas possible de partager des composants UI par défaut.
React Native Web est une implémentation des composants et APIs React Native, compatible avec React DOM.
L'installation est très simple : il suffit d'installer react-native-web en dépendance de l'application web, et d'ajouter un alias de react-native vers react-native-web dans la configuration du bundler. Il est alors possible d'importer tout composant codé en React Native dans son application web React.
Nous avons utilisé React Native Web sur plusieurs projets, ce qui nous a permis de partager entre 75% et 95% de code entre le web et le mobile. Notre retour d’expérience est très positif. Il est toutefois important de garder deux choses en tête :
Il peut être intéressant d’utiliser cet outil à condition de définir une stratégie claire, qui permet de dissocier quels sont les composants qui l'utilisent de ceux qu’il sera nécessaire de coder manuellement pour chaque plateforme.
React Native Web est utilisée par de nombreuses entreprises, comme Twitter (dont faisait partie le créateur de la librairie) et Uber. Cette librairie est également supportée par Expo, qui l'inclut par défaut dans ses applications.
En revanche, cette solution a été essentiellement développée par un seul développeur, ce qui présente un risque au niveau de la maintenance. Bien qu'il soit raisonnable de penser que les entreprises l’utilisant pourraient reprendre la maintenance, ce risque en lien avec la maintenance nous fait positionner React Native Web dans la section trial.
La création d’animations en React Native est parfois difficile. Bien que l'API Animated, fournie avec React Native soit simple, le code est impératif et a tendance à complexifier les composants. Le coût d'implémentation est élevé et par conséquent, les animations ne sont pas priorisées dans le développement de nos applications.
La librairie Reanimated change la donne à partir de la version 2. Grâce à son approche plus déclarative, les animations deviennent très simples à implémenter. Le code d'animation ne nuit pas à la lisibilité du reste du composant. En cas d'animation plus complexe, l'approche par les hooks de Reanimated permet d’extraire facilement le bloc complexe dans un hook isolé. Les hooks permettent également d'extraire les animations en blocs réutilisables.
De plus, les Layout Transitions de Reanimated rendent les animations d'apparition, de disparition ou de changement d’un élément dans une liste peu complexes. Il s’agirait de tâches très complexes sans librairie.
Reanimated va exécuter le code Javascript des animations dans le thread UI. Cela va permettre d’obtenir des animations fluides, sans problème de performance.
Globalement, Reanimated améliore la qualité du code d'animation par rapport à Animated de React Native. De plus, cette librairie améliore la qualité du produit en incitant à implémenter des animations.
Malgré les bénéfices portées par les animations pour le produit, nous ne positionnons Reanimated qu'en "trial" car nous observons encore quelques problèmes. A titre d’exemple, sur certains projets, nos équipes ont observé des crashes ou des animations qui ne se déclenchent pas sur les builds release. Cependant, la librairie évolue vite : les nouvelles features arrivent assez vite et des bugs sont résolus fréquemment.
Sur les cinq dernières années, Typescript a changé le paysage javascript, en accédant même au top-5 des langages les plus utilisés en 2022 dans le sondage stack-overflow.
Sa promesse est de vérifier que si les données en entrée sont bonnes, les données en sortie le seront également. Cela débouche sur plusieurs interrogations :
Qui vérifie que les données en entrée sont correctes et comment intégrer cette vérification pendant l'exécution de notre application, alors que la vérification du typage se fait pendant la phase de compilation ?
La librairie Runtypes permet de générer des types et validateurs associés à partir d'un schéma de la donnée. Ces validateurs permettent de vérifier que les données reçues sont bien conformes au type attendu par une application et de la protéger d'une donnée mal formatée.
Nous vous recommandons de mettre en place un système de validation de types afin d'assurer la sécurité du typage de bout en bout. La bibliothèque Runtypes est très puissante, parfois trop (Contracts, Branded Types, Constraints) ce qui peut être à double tranchant : si les développeurs séniors peuvent s'en servir à juste titre, cela complexifie l'onboarding des développeurs juniors. Nous vous conseillons d'introduire Runtypes petit à petit, en limitant son utilisation au strict nécessaire pour commencer.
Les technologies web et mobile actuelles (Flutter, Jetpack Compose, SwiftUI, React, Vue) nous encouragent à pratiquer le design atomique. Cette méthode de conception consiste à construire nos UI en partant des composants plutôt que des pages.
Storybook est un outil de développement d’interfaces graphiques en isolation disponible en web et React Native, qui répond à la dynamique du design atomique. Il met à disposition des développeurs un environnement (les stories) dans lequel les composants peuvent être développés indépendamment de l'application dans laquelle ils doivent être utilisés. Storybook permet ainsi d’améliorer la parallélisation des développements et d’augmenter la robustesse des composants, avec un focus “component first”. Cet outil permet également de documenter et de structurer les composants du design system. Malheureusement le tooling storybook en React Native n’est pas aussi avancé qu’en web (Storybook server, chromatic).
Addon React Native Web permet de configurer Storybook pour afficher les projets React Native en utilisant React Native Web. C’est une solution très intéressante si votre projet React Native a vocation à être compatible web. Dans le cas contraire, elle constitue un coût de développement et de maintenance conséquent.
Contrairement à l'environnement web qui vous permet de publier votre storybook en site statique, React Native requiert de builder et déployer une application dédiée sur une plateforme de distribution (par exemple AppCenter) pour rendre votre storybook accessible à l’ensemble des parties prenantes du projet.
Enfin, des versions de Storybook/React Native posent des problèmes de compatibilité avec Storybook, rendant la mise à jour de la dépendance compliquée. A titre d'exemple, à l’heure où nous rédigeons ce document, Storybook/React Native n'est pas compatible avec la version 6.4 de Storybook.
Chez BAM, les retours d’expérience sur cette librairie varient d’un projet à l’autre. Pour certains projets, nos équipes ont rencontré des problématiques de déploiement liées à storybook, ce qui les a contraint à concevoir un système de gestion de bibliothèque de composants intégralement. A l’inverse, d’autres projets utilisant React Native et React Native Web ont pu tirer profit des avantages du tooling de chacune des plateformes pour améliorer le développement et la validation de leur design system.
Pour cette raison, nous avons positionné Storybook en trial. Une chose est sûre : l'écosystème autour de Storybook évolue rapidement. L'écart entre le tooling React Native et web devrait donc réduire avec le temps.
Un state global est important pour éviter des incohérences UX, comme un counter de messages non lu pas synchrone avec les messages. Mais React ne propose pas de gestion de state global : il est possible de propager un state local à travers l'arbre de composant via les "props" mais c'est très lourd et peu maintenable.
Redux propose une première solution à ce problème. Via une approche flux unidirectionnel il permet de concevoir un state global. Nous l'avons adopté en 2016, au point de l'utiliser par défaut sur tous nos projets. Nous avons cependant constaté deux problèmes :
Entre-temps, l'écosystème React a évolué. A titre d’exemple, les hooks simplifient la syntaxe pour gérer le state local. Et React-Query ou Apollo, qui permettent de gérer les appels API, facilitent la gestion des données distantes.
Dans ce contexte, Redux est moins utile et des alternatives plus simples et plus modernes ont émergé. Plutôt qu’un grand state monolithique, nous recommandons une approche atomique telle que le hook useState de React : dans un fichier partagé on définit un atom (exemple const userAtom = atom(null)) et on peut ensuite remplacer les useState locaux par useAtom(userAtom).
Cette alternative est plus simple, plus proche de l'API des hooks de state react, et demande moins de conception en amont pour designer le state.
Sur nos projets, nous avons utilisé Recoil et Jotai qui partagent une approche similaire.
Nous préférons Jotai pour plusieurs raisons :
Nous pensons que l'approche de Jotai est complémentaire avec l'utilisation de React-Query ou d'Apollo pour gérer le state distant et que 95% des projets n'ont plus besoin de Redux.
Nous vous conseillons d'essayer la combinaison "Jotai" et "React-Query"/"Apollo" pour votre prochain projet.
React Native Skia est une librairie de rendu graphique 2D haute performance. Elle se base sur Skia, le moteur de rendu graphique utilisé par Flutter et Jetpack Compose.
Cette librairie permet notamment d'afficher des images vectorielles, des graphes ou encore des filtres, du floutage et des shaders sur vos applications. Enthousiasmant d’observer tout ce que la communauté React Native réalise avec cette librairie, n’est-ce pas ?
Seul bémol : React Native Skia est en version "alpha" et la documentation actuelle mentionne que son utilisation doit faire l’objet de précautions. Ainsi, nous ne recommandons pas l’usage de cette librairie pour un projet en production à ce stade.
Cette librairie nous semble cependant être une solution prometteuse et performante qui pourrait, à terme, remplacer d’autres librairies comme react-native-svg. Nous avons en effet identifié des problèmes de performance relatifs à cette librairie.
Utiliser une base de données relationnelle dans une application mobile présente de nombreux avantages : dénormalisation, consistance, contraintes du schéma, transactions, index optimisant l'accès ou encore recherche textuelle...
Quelles sont les solutions les plus adéquates en React Native ?
Watermelon est un module natif pour React Native qui permet de lire, sauvegarder et rechercher de la donnée de façon réactive depuis la bibliothèque SQLite, qui constitue la base de données la plus utilisée au monde. Très utilisée dans l’éco-système embarqué et desktop, elle est également de plus en plus exploitée en mobile avec Room sur Android et Core Data sur iOS.
WatermelonDB fonctionne avec le paradigme reactif : en d’autres termes, lorsqu’une ligne change dans la base de données, les composants qui en dépendent sont automatiquement re-rendus.
Enfin, la base de données Watermelon permet la synchronisation avec un serveur distant : pour implémenter une meilleure expérience hors ligne, il suffit de fournir deux routes côté serveur pour tirer et pousser les changements en local.
Ce module présente cependant deux freins :
Nous vous invitons donc à surveiller les futures évolutions de cette libraire pour l'intégrer lorsqu'elle sera stabilisée.
Annoncée depuis 2018, la nouvelle architecture de React Native est enfin disponible et activable. Elle permet ce qui était jusqu'alors impossible, notamment :
Pour migrer vers cette nouvelle architecture, il suffit de changer un paramètre de configuration dans votre app. A noter qu’il faut tout de même que toutes les librairies de vos projets supportent cette nouvelle architecture.
Certaines d’entre elles, comme reanimated ou react-navigation ont déjà migré ou entamé la migration, mais cela ne s’est pas encore étendu à la plupart des librairies.
De plus, le fait d’activer la nouvelle architecture rallonge considérablement les temps de build natif (jusqu'à x5 en version 0.69), ce qui peut ralentir l'équipe. Nos tests traduisent également une dégradation de la performance sur certains composants, comme la FlatList, qui dans notre expérience en interne, consomme 20% de CPU en plus avec Fabric. Des alternatives à FlatList en cours de création (Wishlist de Margelo et FlashList de Shopify) pourraient cependant pallier ces problèmes.
Notre recommandation : cette nouvelle architecture n’est pas encore prête à être activée. Meta travaille cependant d'arrache pied pour améliorer la documentation et faciliter la migration des librairies et projets React Native. On espère donc pouvoir passer Fabric en "Adopt" dans notre prochain Tech Radar dans 6 mois !
Les formulaires font partie des fonctionnalités présentes dans la quasi-totalité des applications. Conçus et pensés de manière très simple, il peut être tentant de vouloir les développer sans utiliser de librairie.
Pourtant, un formulaire n'est jamais simple. Il y a énormément d'états et de cas limites à prendre en compte pour chaque champ tels que : leur valeur, leur état de remplissage, leur validation, leur état d'erreur ou encore leur formatage.
Nous observons régulièrement des projets, pour lesquels les formulaires ont été délibérément développés sans librairie. Ces projets sont sujets à présenter des défauts de qualité, et à des formulaires dont la complexité va exploser avec le temps.
Pourtant, il existe aujourd'hui plusieurs librairies React qui facilitent grandement leur implémentation. Nous avons par exemple l'habitude d'utiliser les librairies Formik et React Hook Form, avec lesquelles nous avons eu de bonnes expériences. Ces librairies présentent plusieurs avantages : elles sont faciles à prendre en main grâce à une bonne documentation, et leur compatibilité avec des librairies comme Yup facilite la validation des formulaires. Pour ces raisons, nous déconseillons fortement le choix de développer des formulaires sans librairie.
Pas si simple d'implémenter des appels APIs : à titre d’exemple, comment gérer les cas d'erreurs ou de chargement et ajouter un timeout ou un retry ?
Pendant plusieurs années, nous avons utilisé la librairie redux-saga pour répondre à ces problématiques. Depuis 2017, elle s’est distinguée à la fois pour la simplicité d'écriture de ses cas d'usage alors que la syntaxe async/await n'était pas encore répandue, mais aussi pour sa testabilité face à redux-thunk.
Cette solution reste cependant difficile à maîtriser pour plusieurs raisons :
De plus, React a évolué depuis 2017. Avec l’apparition des hooks en 2019 et l’arrivée de Suspense, de nouvelles solutions comme react-query commencent à émerger. Plus simples d’utilisation, elles répondent également à la majorité des besoins en asynchrones de nos projets.
En parallèle, redux-saga reste néanmoins un outil très puissant pour gérer une logique asynchrone complexe, avec toutes les bases pour en faire un système résilient : notamment avec spawning et le redémarrage automatique des sagas en cas d'erreur. A l’instar de techniques utilisées dans le monde des télécommunications (ex: Erlang, Akka stream). Elles requièrent néanmoins des connaissances techniques pointues et ces cas restent rares en pratique.
A l’heure actuelle, nous sommes d’avis de chercher une alternative plus simple que cette librairie, que nous ne recommandons pas d’installer par défaut.
En React Native, le style des composants se fait par défaut avec des StyleSheets. Cependant, l’utilisation de StyleSheets n’est pas adaptée lorsqu'on utilise un thème dynamique ou des styles conditionnés.
Styled-components est une solution de CSS-in-JS bien connue de l’écosystème web, qui inclut notamment une implémentation React Native. Son utilisation permet de profiter de nombreuses fonctionnalités telles que le theming, les styles en tant qu’objets ou encore la configuration du style via les props.
Styled-components nous encourage à séparer nos composants d’UI de nos smart components, favorisant ainsi la séparation des responsabilités.
Cependant, la librairie présente plusieurs inconvénients liés au typage. Nous avons observé des problèmes de performance rédhibitoires sur l’auto-complétion au niveau de l'IDE.
Ce problème a été identifié et corrigé à plusieurs reprises dans le passé. Malheureusement, il reste toujours d’actualité, ce qui n’est pas rassurant sur le court terme.
Nous recommandons de privilégier la bibliothèques CSS-in-JS Emotion, qui possède une
api très similaire à Styled-components, ce qui simplifie grandement la migration. Emotion est une solution plus légère et plus rapide que Styled-components, et nous n’avons constaté aucun problème de typage à ce stade.
Retrouvez l'avis de nos experts sur les techniques, plateformes, outils, langages et frameworks associés aux principales technologies mobiles que nous utilisons au quotidien chez BAM : React Native, Flutter et Native.