Les applications complexes nécessitent de l’organisation. C’est indispensable pour qu’elles soient maintenable.

Typer les routes sur React-native permet de simplifier leurs développements.

Dans un écran, nous connaîtrons les paramètres disponibles et sur quels écrans nous pouvons nous rendre.

Cet article s’adresse aux développeurs ayant déjà des bases en TypeScript.

Pour l’exemple, nous créons un mini projet sur Expo avec une barre de navigation qui amènera vers 4 écrans :

  1. Welcome qui est l’écran d’accueil
  2. Products qui pourra prendre un paramètre optionnel « search » de type string
  3. Sessions qui sera accessible depuis la pile d’écran racine
  4. Settings qui sera accessible depuis la barre de navigation

Sommaire

Mise en place du projet avec Expo

Vous devez installer l’application Expo Go sur votre téléphone.

On créé ensuite notre application mobile en nous basant sur le template « expo-template-blank-typescript » :

npx create-expo-app -t expo-template-blank-typescript

Installation des dépendances requises par react-navigation

npm install @react-navigation/native npm install @react-navigation/bottom-tabs npm install @react-navigation/stack npx expo install react-native-screens react-native-safe-area-context npx expo install react-native-gesture-handler

Versions des dépendances et devDependencies

dépendances

  • « @react-navigation/bottom-tabs »: « ^6.5.11 »,
  • « @react-navigation/native »: « ^6.1.9 »,
  • « @react-navigation/stack »: « ^6.3.20 »,
  • « expo »: « ~49.0.15 »,
  • « expo-status-bar »: « ~1.6.0 »,
  • « react »: « 18.2.0 »,
  • « react-native »: « 0.72.6 »,
  • « react-native-safe-area-context »: « 4.6.3 »,
  • « react-native-screens »: « ~3.22.0 »,
  • « react-native-gesture-handler »: « ~2.12.0 »

devDependencies

  • « @babel/core »: « ^7.20.0 »,
  • « @types/react »: « ~18.2.14 »,
  • « typescript »: « ^5.1.3 »

Configuration des fichiers tsconfig.json et babel.config.js

Nous modifions les fichiers ‘tsconfig.json’ et ‘babel.config.js’ créés par défaut.

Nous ajoutons deux alias vers le dossier src (routes et screens) et le baseUrl pour faciliter les imports.

tsconfig.json
{ "extends": "expo/tsconfig.base", "compilerOptions": { "strict": true, "baseUrl": ".", // Base directory "paths": { "@routes/*": ["./src/routes/*"], // Alias "@screens/*": ["./src/screens/*"] // Alias } } }

babel.config.js
module.exports = function (api) { api.cache(true); return { presets: ['babel-preset-expo'], plugins: [ [ 'module-resolver', { alias: { '@routes': './src/routes', '@screens': './src/screens', }, }, ], ], }; };

Le dossier src ainsi que les dossiers routes et screens doivent être créés.

Point d’entrée du projet

Le fichier d’entrée ‘App.tsx’ est déjà créé, nous y ajoutons le composant NavigationContainer.

Ce composant initialise les différentes routes.

App.tsx
import { NavigationContainer } from '@react-navigation/native'; export default function App() { return ( <NavigationContainer> <View style={styles.container}> <Text>Open up App.tsx to start working on your app!</Text> <StatusBar style='auto' /> </View> </NavigationContainer> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#fff', alignItems: 'center', justifyContent: 'center', }, });

Création des écrans

Nous créons 4 écrans : Welcome, Products, Settings et Sessions.

  • Welcome, Products et Settings seront affichés dans la barre de navigation.
  • Sessions est sur une autre stack.
Typer les routes sur react-native Capture decran 2023 12 19 a 10.23.59
Architecture des écrans

Typer les routes sur React-native

Déclaration des paramètres des écrans

On crée le fichier de types pour typer les routes sur React-native.

src/routes/types.ts
import type { NavigatorScreenParams } from '@react-navigation/native'; export type RootStackParamList = { Home: NavigatorScreenParams<HomeTabParamList>; Sessions: undefined; }; export type HomeTabParamList = { Welcome: undefined; Products?: { search: string; }; Settings: undefined; };

RootStackParamList est un type qui est utilisé par la Stack principale (généralement appeler Root) qui contient : Home et Sessions.

NavigatorScreenParams est un type générique qui attend un autre type avec le nom des écrans et leurs paramètres ou une nouvelle pile d’écran (Nested navigator)

Sessions est une route qui ne contient pas de paramètres. React-navigation nous indique qu’une route sans paramètre doit avoir comme type undefined.

HomeTabParamList est le type pour la barre navigation. Il affiche trois éléments. Welcome, Products et Settings. L’écran Products peut recevoir un paramètre « search » qui n’est pas obligatoire.

Déclarations des props utilisables dans les écrans

Nous avons ajouté les paramètres dédiés à chaque écran.

Nous définissons maintenant les types des props utilisables dans chaque écran.

Pour cela, on ajout deux types : RootStackScreenProps et HomeTabScreenProps

src/routes/types.ts (suite)
import type { NavigatorScreenParams, CompositeScreenProps } from '@react-navigation/native'; import type { BottomTabScreenProps } from '@react-navigation/bottom-tabs'; import type { StackScreenProps } from '@react-navigation/stack'; export type RootStackParamList = { Home: NavigatorScreenParams<HomeTabParamList>; Sessions: undefined; }; export type HomeTabParamList = { Welcome: undefined; Products?: { search: string; }; Settings: undefined; }; export type RootStackScreenProps<T extends keyof RootStackParamList> = StackScreenProps<RootStackParamList, T>; export type HomeTabScreenProps<T extends keyof HomeTabParamList> = CompositeScreenProps< BottomTabScreenProps<HomeTabParamList, T>, RootStackScreenProps<keyof RootStackParamList> >;

Pas de panique, je vais expliquer chaque type en détail !

RootStackScreenProps est un type générique T qui doit être une clé valide de RootStackParamList (Home ou Sessions).

HomeTabScreenProps est similaire à RootStackScreenProps sauf qu’il va utiliser les types fournis par react-navigation.

BottomTabScreenProps est typé par deux paramètres :

  1. La liste des différents écrans (HomeTabParamList). Qui correspond à notre barre de navigation.
  2. Le nom de l’écran de cette liste que l’on va appelé. Défini par le type générique T (T extends keyof HomeTabParamList).

CompositeScreenProps est un type générique fournit par react-navigation qui prend deux paramètres :

  1. Le type de la pile de navigation de l’écran courant
  2. Le type de la pile de navigation du parent.

Dans notre exemple, le parent est RootStackScreenProps.

Création de la Stack et la barre de navigation

Une fois les types créés, il est temps de les utiliser dans nos écrans respectifs.

Tout en haut du fichier, nous ajoutons l’importation de react-native-gesture-handler.

Nous remplaçons le contenu du fichier ‘App.tsx’ par celui-ci :

App.tsx
import 'react-native-gesture-handler'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import { NavigationContainer } from '@react-navigation/native'; import { createStackNavigator } from '@react-navigation/stack'; import { HomeTabParamList, RootStackParamList } from '@routes/types'; import Products from '@screens/Products'; import Settings from '@screens/Settings'; import Welcome from '@screens/Welcome'; import Sessions from '@screens/sessions/Sessions'; const Tab = createBottomTabNavigator<HomeTabParamList>(); const Stack = createStackNavigator<RootStackParamList>(); function BottomTabBar() { return ( <Tab.Navigator> <Tab.Screen name='Welcome' component={Welcome} /> <Tab.Screen name='Products' component={Products} /> <Tab.Screen name='Settings' component={Settings} /> </Tab.Navigator> ); } export default function App() { return ( <NavigationContainer> <Stack.Navigator screenOptions={{ headerShown: false }}> <Stack.Screen name='Home' component={BottomTabBar} /> <Stack.Screen name='Sessions' component={Sessions} options={{ headerShown: true }} /> </Stack.Navigator> </NavigationContainer> ); }

L’importation de ‘react-native-gesture-handler’ est un prérequis pour le bon fonctionnement de « @react-navigation/stack« 

Pour l’utilisation de la Stack, nous indiquons au composant qu’il recevra comme props le type RootStackParamList.

Le type RootStackParamList peut seulement contenir Home et Sessions.

Grâce au type, nous aurons également l’autocomplétion pour les écrans liés à RootStackParamList.

Voyons ce qui se passe si nous ajoutons un écran qui n’existe pas.

Ajout d’un écran qui n’existe pas

Typer les routes sur React-native
Image détaillant une erreur typescript

TypeScript nous assiste et nous indique que l’écran ScreenNotExist n’existe pas pour le type RootStackParamList.

Edition de l’écran Welcome.tsx

Welcome.tsx
import type { HomeTabScreenProps } from '@routes/types'; import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; const Welcome = ({ route, navigation }: HomeTabScreenProps<'Welcome'>) => { return ( <View> <TouchableOpacity style={styles.buttonProducts} onPress={() => navigation.navigate('Products', { search: 'ref-123', }) }> <Text>Navigate to Products with query ref-123</Text> </TouchableOpacity> <TouchableOpacity style={styles.buttonProducts} onPress={() => navigation.navigate('Products')}> <Text>Navigate to Products without params</Text> </TouchableOpacity> <TouchableOpacity style={styles.buttonSettings} onPress={() => navigation.navigate('Settings')}> <Text>Navigate to Settings without parameters</Text> </TouchableOpacity> <TouchableOpacity style={styles.buttonSettings} onPress={() => navigation.navigate('Sessions')}> <Text>Navigate to Sessions</Text> </TouchableOpacity> </View> ); }; export default Welcome; const styles = StyleSheet.create({ buttonProducts: { backgroundColor: 'red', padding: 10, borderRadius: 10, marginTop: 20, }, buttonSettings: { backgroundColor: 'lightblue', padding: 10, borderRadius: 10, marginTop: 20, }, });

L’écran Welcome est typé via HomeTabScreenProps<‘Welcome’>.

Il hérite des props route et navigation fournis par CompositeScreenProps qu’on a vu un peu plus haut.

Notre éditeur de code, nous indique les routes disponibles lorsqu’on souhaite naviguer vers d ‘autres écrans.

Props de l’écran Products.

Voyons un exemple plus concret avec Products :

Typer les routes sur react-native Capture decran 2023 12 19 a 12.10.13
Image montrant l’autocomplétion des paramètres de l’écran Products
Typer les routes sur react-native Capture decran 2023 12 19 a 12.11.48
Image montrant l’autocomplétion des routes disponibles

Si on souhaite accéder aux props depuis un composant de l’écran Products, on doit utiliser le hook useRoute() ou useNavigation() fournis par react-navigation. Voici un exemple d’utilisation :

Products.tsx
import { useNavigation, useRoute } from '@react-navigation/native'; import { HomeTabScreenProps } from '@routes/types'; import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; const MyComponentInProduct = () => { const { params } = useRoute<HomeTabScreenProps<'Products'>['route']>(); const navigation = useNavigation<HomeTabScreenProps<'Products'>['navigation']>(); return ( <View> <Text>Params from component MyComponentInProduct: {JSON.stringify(params?.search, null, 4)}</Text> <TouchableOpacity onPress={() => navigation?.navigate('Settings')} style={styles.button}> <Text>Go to Settings from hook</Text> </TouchableOpacity> </View> ); }; const Products = ({ route, navigation }: HomeTabScreenProps<'Products'>) => { return ( <View> <Text>Params from Home</Text> <Text>{JSON.stringify(route?.params?.search, null, 4)}</Text> <MyComponentInProduct /> </View> ); }; export default Products; const styles = StyleSheet.create({ button: { backgroundColor: 'lightblue', padding: 10, borderRadius: 10, marginTop: 20, }, });

Revenir à la Stack racine

Sessions.tsx
import { RootStackScreenProps } from '@routes/types'; import React from 'react'; import { Text, View } from 'react-native'; const Sessions = ({ route, navigation }: RootStackScreenProps<'Sessions'>) => { React.useEffect(() => { setTimeout(() => { navigation.navigate('Home', { screen: 'Products', params: { search: 'ref-456', }, }); }, 2000); }, []); return ( <View> <Text>Sessions</Text> </View> ); }; export default Sessions;

Cet exemple couvre une grande majorité des cas pour typer les routes sur React-native.

On peut retrouver dans la doc de react-navigation d’autres exemples pour implémenter un drawer …

Conclusion

Les types demandent un effort d’organisation et de développement au départ, mais ils vous facilitent le développement par la suite (reprise d’un code existant, nouvel arrivant sur un projet, éviter des bugs, …).