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 :
- NavigatorIOS
- react-navigation
- react-native-navigation de Wix (v1 & v2)
- AirBNB's native-navigation (je plaisante, il n'a jamais été vraiment actif). Avec AirBNB qui met fin à React Native, je doute qu'il le soit un jour.
- react-native-router-flux
- react-native-easy-router
- react-native-swipe-navigation
- react-router-native
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
- Si vous êtes novice en React Native, mais que vous souhaitez créer une application de production, choisissez react-navigation.
- Si vous avez une bonne connaissance de React Native et que vous savez que votre application aura besoin d'une haute performance (interactions de rotation étendues, ou utilisation approfondie de vos piles, par exemple), choisissez react-native-navigation v2.
- Si vous désirez porter votre application web en utilisant react-router, choisissez react-router-native.
- Si vous voulez simplement jouer avec quelque chose de nouveau, choisissez react-native-swipe-navigation.
- Vous pouvez trouver le code des exemples suivants sur Github.
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.
Navigation native vs navigation JS
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 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 :
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 🍏📱
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 deUINavigationController
, vous permet d'implémenter une pile de navigation. Il fonctionne de la même manière que dans une application native utilisantUINavigationController
, 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>
);
}
}
Nous voyons qu'il est natif, car toutes les vues sont vierges pour les écrans qui sont sous le 1er de la pile :
Comme on pouvait s'y attendre à la lecture de son nom, l'homologue Android n'effectue aucun rendu. Duh.
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 🤖🍏🌎
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 .
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 🤖🍏📱
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>
);
}
}
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-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-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-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-router-native 🤖🍏🌎
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>
);
}
}
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 !