Aller au contenu principal
Retour au blogue

Développement logiciel

Solutions de navigation React Native en 2018

Adrien Thiery
26 oct. 2018 ∙ 16 mins
Écran d'ordinateur

Lorsque vous démarrez votre application React Native aujourd'hui, compte tenu de la diversité de l'écosystème des paquets, il est tout à fait normal de ne pas savoir quelle bibliothèque utiliser, notamment pour la navigation.

Il existe de nombreuses solutions :

Il en existe d'autres, mais j'ai sélectionné celles qui semblaient les plus populaires (et celles dont j'ai entendu parler le plus récemment) pour cette comparaison.

Révélation: les seules bibliothèques utilisées en production chez Osedea sont react-navigation, react-native-navigation et react-native-router-flux.

TL;PR

En quoi consiste cet article

Dans cet article, nous allons mettre en œuvre un schéma de navigation simple (Stack navigation) avec toutes ces bibliothèques pour comparer leur fonctionnement et leurs atouts.

Tous les exemples ont été créés avec la plus récente version de React Native , soit, au moment de la rédaction de cet article (0.57.2), à l'exception de ReactNativeNavigationV2, car la v2 de react-native-navigation de wix ne supporte que la 0.56.* comme version la plus élevée de React Native à l'heure actuelle.

Mais d'abord, une petite explication concernant les différences entre les deux grandes familles de bibliothèques de navigation.

Dans une application mobile native, chaque écran possède un conteneur natif (UIViewController sur iOS, Activity sur Android).

Cela permet aux systèmes iOS et Android de faire des optimisations pour économiser un peu de CPU ou de mémoire.

Le système de pont de React Native nous permet d'utiliser des composants natifs, par exemple des composants de navigation (NavBar, bouton Back, etc.).

Le seul problème est que lorsqu'on les utilise ainsi, ils sont hors de la portée de React Root View , ce qui les rend plus difficiles à inspecter (l'inspecteur JS de React Native est en fait intégré dans la Root View de React Native).

Pour visualiser tout cela, quoi de mieux que des images :

La navigation native

La navigation native crée un conteneur Native pour chaque vue (view) - le rectangle à bordure rouge - ce qui permet au système d'optimiser et d'utiliser moins de puissance de traitement et de batterie. Cependant, cela crée une portée React limitée (le rectangle de bordure vert).

En comparaison, la navigation JS est un peu moins optimisée :

la navigation JS
La navigation JS n'a qu'un seul conteneur natif et elle superpose les couches (layers) de vues les unes sur les autres, ce qui est plus lourd à gérer pour le système.

On voit que toutes les vues sont rendues dans le même conteneur natif, superposées les unes aux autres, ce qui rend le traitement de chaque opération un peu plus lent.

Voici une petite légende pour vous aider à regarder les différentes bibliothèques de navigation :

🍏 : La bibliothèque fonctionne sur iOS

🤖 : La bibliothèque fonctionne sur Android

🌎 : La bibliothèque est purement JS

📱 : La bibliothèque utilise des composants natifs

NavigatorIOS est le composant de navigation qui accompagne React Native depuis le début. L'histoire raconte que lorsque Facebook a inséré React Native dans ses applications mobiles natives, il n'a plus eu besoin de composants/utilitaires de navigation.

Pour la forme, regardons comment cela fonctionne.

Comme l'indique la documentation:

NavigatorIOS est un wrapper autour de UINavigationController, vous permet d'implémenter une pile de navigation. Il fonctionne de la même manière que dans une application native utilisant UINavigationController, en fournissant les mêmes animations et le même comportement que UIKit.

La définition de votre route initiale doit être effectuée dans votre composant racine, et transmise comme prop à NavigatorIOS :

// App.js
import { NavigatorIOS } from 'react-native';
import Home from './scenes/Home';
type Props = {};
export default class App extends Component<Props> {
  render() {
    return (
      <NavigatorIOS
        initialRoute={{
          component: Home,
          title: 'Home',
        }}
      />
    );
  }
}

Mais ensuite, chaque composant doit importer les composants vers lesquels il veut naviguer, ce qui peut être très complexe dans certains cas :

// scenes/Home.js
import PushedView from './PushedView';
type Props = {
  navigator: Object;
};
export default class Home extends Component<Props> {
  goToPushedView = () => {
    this.props.navigator.push({
      title: 'PushedView',
      component: PushedView,
    });
  };
  render() {
    return (
      <View>
        <Button onPress={this.goToPushedView} title={'Push something'} />
      </View>
    );
  }
}

Continuez à pousser!

// scenes/PushedView.js
type Props = {
  navigator: object;
};
export default class PushedView extends Component<Props> {
  goToPushedView = () => {
    this.props.navigator.push({
      title: 'PushedView',
      component: PushedView,
    });
  };
  render() {
    return (
      <View>
        <Text>"I'm pushed!"</Text>
        <Button onPress={this.goToPushedView} title={'Push more'} />
      </View>
    );
  }
}

Navigator IOS

Nous voyons qu'il est natif, car toutes les vues sont vierges pour les écrans qui sont sous le 1er de la pile :

Hiérarchie native à 5 écrans poussée avec NavigatorIOS
Hiérarchie native à 5 écrans poussée avec NavigatorIOS

Comme on pouvait s'y attendre à la lecture de son nom, l'homologue Android n'effectue aucun rendu. Duh.

L'homologue Android n'effectue aucun rendu

Cela nous montre que si nous voulons construire une application React Native complète, nous avons besoin d'une bibliothèque externe

Modifier(10/11/2018) : Comme mentionné dans les commentaires de Lorenzo Sciandra, NavigatorIOS sera entièrement supprimé de React Native dans la version 0.58 (voir le React Native Changelog).

react-navigation 🤖🍏🌎

react-navigation
Comment voir si une bibliothèque est Native ou JS? Vérifiez la composition du code github!

En plus d'être celle mentionnée dans la documentation officielle, c'est la bibliothèque de navigation la plus utilisée de toutes (13556 ⭐️ sur GitHub au moment de la rédaction).

L'installation est aussi simple que :

npm install react-navigation
// OR
yarn add react-navigation

Et le tour est joué!

La définition de vos routes passe par une fonctioncreate<Choose your pattern>Navigator(createStackNavigator, createBottomTabsNavigator, etc.)

// App.js
import { createStackNavigator } from 'react-navigation';
import Home from './scenes/Home';
import PushedView from './scenes/PushedView';
export default createStackNavigator({
  Home: {
    screen: Home,
    navigationOptions: {
      title: 'Home',
    },
  },
  PushedView: {
    screen: PushedView,
  },
});

La navigation est en fait aussi simple qu'il n'y paraît ! Il suffit d'utiliser l'accessoire de navigation injecté par react-navigation dans chaque composant Screen.

// scenes/Home.js
type Props = {
  navigation: Object;
};
export default class Home extends Component<Props> {
  goToPushedView = () => {
    this.props.navigation.navigate('PushedView');
  };
  render() {
    return (
      <View>
        <Button onPress={this.goToPushedView} title={'Push something'} />
      </View>
    );
  }
}

Continuez à pousser!

// scenes/PushedView.js
type Props = {
  navigation: Object;
};
export default class PushedView extends Component<Props> {
  goToPushedView = () => {
    this.props.navigation.push('PushedView');
  };
  render() {
    return (
      <View>
        <Text>"I'm pushed!"</Text>
        <Button onPress={this.goToPushedView} title={'Push more'} />
      </View>
    );
  }
}

Vous remarquerez que nous passons de la navigation à pousser dans le PushedView. Cela ajoute une petite distinction : lors de la navigation, si vous essayez de visiter le même écran deux fois, cela ne fonctionnera pas. navigate fait en sorte que votre écran se comporte comme un singleton dans la pile. En revanche, en utilisant push, nous pouvons ajouter de nouveaux écrans sur notre pile, et ce à l'infini .

Utilisation de React Navigation

L'homologue Android
Comme prévu, le même code fonctionne parfaitement sur Android, mais en utilisant les modèles de conception Android

 les autres vues de la pile ne sont pas optimisées.
Bien que nous voyions dans XCode que les autres vues de la pile ne sont pas optimisées.

Modifier(10/11/2018) : Comme mentionné par Brent Vatne dans les commentaires, cela peut être optimisé en utilisant react-native-screens et sera la valeur par défaut dans react-navigation à l'avenir. (Voir ce tweet de Janic Duplessis pour la visualisation XCode).

react-native-navigation 🤖🍏📱

 react-native-navigation

blogpost rnn image 10

React Native Navigation fournit une navigation sur plateforme 100% native à la fois sur iOS et Android pour les applications React Native.

V1 ou V2?

À l'heure actuelle, le choix est difficile, car la version 1 (v1) n'est plus maintenue, mais fonctionne bien, malgré quelques bizarreries, et la version 2 (v2) est toujours en alpha.

Modifier(19/11/2018) : la v2 est maintenant sortie de l'alpha!

La partie la plus difficile est en fait au début : la configuration.

Setup (v1)

Puisque c'est natif, nous devons configurer quelques trucs natifs. Je ne vais pas trop rentrer dans les détails car, grâce à Ignite - un super outil vous permettant d'accélérer votre développement React Native - et au plugin ignite-native-navigation, la douleur est passée.

ignite attach # to attach to your React Native project
ignite add native-navigation

Vous êtes prêt à partir!

Enregistrement de vos routes (v1)

Dans ce cas, plus de AppRegistry.registerComponent() dans notreindex.js, react-native-navigation gère le démarrage de l'application d'une manière différente!

// index.js
import { Navigation } from 'react-native-navigation';
import Home from './scenes/Home';
import PushedView from './scenes/PushedView';
Navigation.registerComponent('ReactNativeNavigationV1.Home', () => Home);
Navigation.registerComponent('ReactNativeNavigationV1.PushedView', () => PushedView);
Navigation.startSingleScreenApp({
  screen: {
    screen: 'ReactNativeNavigationV1.Home',
    title: 'Home',
  },
});

Le reste est très similaire à react-navigation :

// scenes/Home.js
type Props = {
  navigator: Object;
};
export default class Home extends Component<Props> {
  goToPushedView = () => {
    this.props.navigator.push({
      screen: 'ReactNativeNavigationV1.PushedView',
      title: 'PushedView',
    });
  };
  render() {
    return (
      <View>
        <Button onPress={this.goToPushedView} title={'Push something'} />
      </View>
    );
  }
}

Et nous pouvons continuer à pousser :

// scenes/PushedView.js
type Props = {
  navigator: Object;
};
export default class PushedView extends Component<Props> {
  goToPushedView = () => {
    this.props.navigator.push({
      screen: 'ReactNativeNavigationV1.PushedView',
      title: 'PushedView',
    });
  };
  render() {
    return (
      <View>
        <Text>"I'm pushed!"</Text>
        <Button onPress={this.goToPushedView} title={'Push something'} />
      </View>
    );
  }
}

react-native-navigation navigation native simple pour React Native sur iOS et Android
react-native-navigation permet une navigation native simple pour React Native sur iOS et Android.

Structure de la mise en page sur iOS après 5 « push »
Structure de la mise en page sur iOS après 5 « push »

Configuration (v2)

Pour la version 2, malheureusement aucun outil magique ne peut nous venir en aide. J'ai essayé d'utiliser ignite-react-native-navigation pour tricher un peu, sans succès. Je suppose que nous devons suivre le guide pour celui-ci.

Après avoir suivi le guide très attentivement, votre application devrait démarrer correctement.

// index.js
import { Navigation } from 'react-native-navigation';
import Home from './scenes/Home';
import PushedView from './scenes/PushedView';
Navigation.registerComponent(`ReactNativeNavigationV2.Home`, () => Home);
Navigation.registerComponent(`ReactNativeNavigationV2.PushedView`, () => PushedView);
Navigation.events().registerAppLaunchedListener(() => {
  Navigation.setRoot({
    root: {
      stack: {
        children: [
          {
            component: {
              name: 'ReactNativeNavigationV2.Home',
              options: {
                topBar: {
                  title: {
                    text: 'Home',
                  },
                },
              },
            },
          },
        ],
      },
    },
  });
});

Nous pouvons déjà constater que le code est beaucoup plus volumineux, puisqu'un seul écran avec un titre nécessite un grand nombre de lignes et d'objets imbriqués. Toutefois, c'est pour des raisons de lisibilité et de personnalisation.

// screens/Home.js (PushedView.js is highly similar)
import { Navigation } from 'react-native-navigation';
type Props = {};
export default class Home extends Component<Props> {
  goToPushedView = () => {
    Navigation.push(this.props.componentId, {
      component: {
        name: 'ReactNativeNavigationV2.PushedView',
        options: {
          topBar: {
            title: {
              text: 'PushedView',
            },
          },
        },
      },
    });
  };
  render() {
    return (
      <View>
        <Button onPress={this.goToPushedView} title={'Push something'} />
      </View>
    );
  }
}

react-native-navigation navigation native simple
Exactement la même chose que v1, sauf le code.

Structure de la mise en page
Mêmes optimisations (un seul en-tête)

react-native-router-flux 🤖🍏🌎

react-native-router-flux

blogpost rnn image 15

react-native-router-flux est une API différente dereact-navigation.

Elle offre un autre moyen de définir vos routes, en JSX directement, comme tout autre composant React.

// App.js
import { Router, Stack, Scene } from 'react-native-router-flux';
import Home from './scenes/Home';
import PushedView from './scenes/PushedView';
const App = () => (
  <Router>
    <Stack key="root">
      <Scene key="home" component={Home} title="Home" />
      <Scene key="pushedView" component={PushedView} />
    </Stack>
  </Router>
);
export default App;

react-native-router-flux
Nous voyons ici que l'implémentation est un peu différente, notamment l'animation de l'en-tête qui ne correspond pas à l'animation native.

react-native-router-flux
C'est Js , c'est sûr!

react-native-easy-router 🤖🍏🌎

react-native-easy-router

yarn add react-native-easy-router

react-native-easy-router est aussi simple qu'il y paraît.

// App.js
import React from 'react';
import Router from 'react-native-easy-router';
import { Text, View } from 'react-native';
import Home from './scenes/Home';
import PushedView from './scenes/PushedView';
const routes = { Home, PushedView };
const App = () => <Router routes={routes} initialRoute="Home" />;
export default App;

Toutefois, il n'offre pas non plus beaucoup de fonctionnalités, alors que c'est là tout l'intérêt.

La navigation est un mélange de react-navigation etreact-native-router-flux:

// scenes/Home.js
type Props = {
  router: Object;
};
export default class Home extends Component<Props> {
  goToPushedView = () => {
    this.props.router.push.PushedView();
  };
  render() {
    return (
      <View>
        <Button onPress={this.goToPushedView} title={'Push something'} />
      </View>
    );
  }
}

Et le résultat est, eh bien, simple. Mais il fonctionne parfaitement.

react-native-easy-router navigation native simple

react-native-easy-router
Nous le voyons ici aussi, c'est du full JS, donc chaque écran est « rendered » en même temps.

react-native-swipe-navigation 🤖🍏🌎

react-native-swipe-navigation

yarn add react-native-swipe-navigation

Cette dernière bibliothèque est assez simple à utiliser, de plus elles proposent le balayage (swipe) comme nouveau motif. Elle offre ainsi la possibilité de pouvoir facilement reproduire une application comme Snapchat.

Elle vous permet de définir un écran pour chaque direction dans laquelle vous pouvez balayer. Elle offre aussi une animation fluide pour la transition entre les écrans.

// App.js
import SwipeNavigator from 'react-native-swipe-navigation';
import Home from './scenes/Home';
import PushedView from './scenes/PushedView';
const Navigator = SwipeNavigator({
  Home: {
    screen: Home,
    right: 'PushedView',
  },
  PushedView: {
    screen: PushedView,
    type: 'push',
    right: 'PushedView',
  },
});
export default Navigator;

Les gestes/mouvements autorisés sont définis globalement à l'initialisation de l'application. Ensuite, dans chaque composant, des callbacks sont utilisés pour autoriser/refuser l'action de balayage.

// scenes/Home.js
type Props = {
  nav: Object;
};
export default class Home extends Component<Props> {
  componentDidMount() {
    const { nav } = this.props;
    nav.onNavigateShouldAllow(() => {
      return true;
    });
    nav.onNavigateRightShouldAllow(() => {
      return true;
    });
  }
  componentWillUnmount() {
    this.props.nav.cleanUp();
  }
  goToPushedView = () => this.props.nav.navigate('PushedView');
  render() {
    return (
      <View>
        <Button onPress={this.goToPushedView} title={'Push something'} />
      </View>
    );
  }
}

react-native-swipe-navigation

react-native-swipe-navigation
Autant de JS que possible!

react-router-native 🤖🍏🌎

react-router-native
Une telle diversité dans la composition du code de base!

react-router-native est en quelque sorte une surprise pour moi puisque je n'en ai pas entendu beaucoup à son sujet.

Étant les bindings natifs de react-router, il apporte quelque chose d'intéressant, surtout pour les équipes qui viennent du web et qui veulent commencer à utiliser React Native, étant donné qu'il vous propose la même interface.

yarn add react-router-native

Comme pour react-router sur le web, les routes sont définies comme des composants React, dans la hiérarchie des vues (views).

// App.js
import { NativeRouter, Route, Link } from 'react-router-native';
import Home from './scenes/Home';
import PushedView from './scenes/PushedView';
type Props = {};
export default class App extends Component<Props> {
  render() {
    return (
      <NativeRouter>
        <View>
          <Route exact path="/" component={Home} />
          <Route path={'/pushed'} component={PushedView} />
        </View>
      </NativeRouter>
    );
  }
}

Pour naviguer d'un écran à l'autre, il suffit d'utiliser le composant Link, qui enveloppe en fait un composant TouchableHighlight, de sorte que vous pouvez le styliser comme vous le feriez avec un TouchableHighlight.

// scenes/Home.js
import { Link } from 'react-router-native';
type Props = {};
export default class Home extends Component<Props> {
  render() {
    return (
      <View style={styles.container}>
        <Link to="/pushed">
          <Text>{'Push something'}</Text>
        </Link>
      </View>
    );
  }
}

react-router-native

react-router-native
Il est amusant de constater que même s'il s'agit de JS, les vues dans la pile semblent être optimisées!

Conclusion

Tout se résume à ce dont vous avez besoin.

Si vous voulez juste une navigation à l'écran, en JS, avec une petite empreinte sur la taille de votre bundle, react-native-easy-router pourrait être le meilleur choix, mais vous devrez gérer l'en-tête vous-même.

Si vous voulez les meilleures performances, même si c'est un peu plus complexe, optez pour react-native-navigation.

Si vous ne savez pas vraiment ce que votre application va devenir, mais que vous voulez de la flexibilité et la possibilité de tout personnaliser, je vous recommande d'opter pour react-navigation.

Si vous avez juste besoin de créer une application iOS simple et que vous ne voulez pas ajouter une autre bibliothèque, utilisez simplement NavigatorIOS.

Si vous portez votre application web React, react-router-native pourrait être la meilleure solution pour vous.

Enfin, si vous voulez cloner Snapchat, optez pour react-native-swipe-navigation.

Vous pouvez trouver l'ensemble de ce code sur Github - Examples de react native navigation.

-

N'hésitez pas à me contacter pour toute question ou demande de renseignements sur Twitter ou sur le site web de Osedea.

Aussi, nous recrutons! 👩🎓👨🎓

N'hésitez pas à nous contacter si vous recherchez des opportunités à Montréal, à Nantes, en France, ou passez prendre un café si vous êtes en ville !