Aller au contenu

Réagir Feuille de chaleur indigène

Réagir autochtone - Apprendre une fois, écrire n'importe où

React Native est un cadre pour la construction d'applications mobiles natives utilisant React. Il permet aux développeurs d'utiliser React avec des capacités de plate-forme native pour construire des applications mobiles pour iOS et Android en utilisant une base de code unique.

Copier toutes les commandes Générer PDF

Sommaire

  • [Installation] (LINK_0)
  • [Pour commencer] (LINK_0)
  • [Composants de base] (LINK_0)
  • [Navigation] (LINK_0)
  • [Gestion de l'État] (LINK_0)
  • [Stylage] (LINK_0)
  • [API et services] (LINK_0)
  • [Modules autochtones] (LINK_0)
  • [Performance] (LINK_0)
  • [Tests] (LINK_0)
  • [Débogage] (LINK_0)
  • [Déploiement] (LINK_0)
  • [Code type] (LINK_0)
  • [Bibliothèques des tiers] (LINK_0)
  • [Meilleures pratiques] (LINK_0)
  • [Dépannage] (LINK_0)

Installation

Développement Environnement

Préalables

# Install Node.js (LTS version recommended)
# Download from https://nodejs.org/

# Install Watchman (macOS)
brew install watchman

# Install Java Development Kit (JDK 11)
# Download from https://adoptopenjdk.net/

# Install Android Studio
# Download from https://developer.android.com/studio

# Install Xcode (macOS only)
# Download from Mac App Store

Réagir à l'IDC autochtone

# Install React Native CLI globally
npm install -g react-native-cli

# Alternative: Use npx (recommended)
npx react-native --version

# Install Expo CLI (for Expo workflow)
npm install -g expo-cli
```_

#### Configuration de développement Android
```bash
# Set environment variables (add to ~/.bashrc or ~/.zshrc)
export ANDROID_HOME=$HOME/Library/Android/sdk
export PATH=$PATH:$ANDROID_HOME/emulator
export PATH=$PATH:$ANDROID_HOME/tools
export PATH=$PATH:$ANDROID_HOME/tools/bin
export PATH=$PATH:$ANDROID_HOME/platform-tools

# Create Android Virtual Device (AVD)
# Open Android Studio > AVD Manager > Create Virtual Device

# Start Android emulator
emulator -avd Pixel_4_API_30
```_

#### Configuration de développement iOS (macOS seulement)
```bash
# Install CocoaPods
sudo gem install cocoapods

# Install iOS Simulator
# Included with Xcode

# Install iOS dependencies
cd ios && pod install && cd ..

Création de projets

Réagir à l'IDC autochtone

# Create new project
npx react-native init MyApp

# Create project with specific template
npx react-native init MyApp --template react-native-template-typescript

# Navigate to project
cd MyApp

# Install dependencies
npm install

# Install iOS dependencies (macOS only)
cd ios && pod install && cd ..

Exposition CLI

# Create new Expo project
expo init MyExpoApp

# Choose template
# - blank: minimal app
# - blank (TypeScript): minimal TypeScript app
# - tabs: app with tab navigation
# - minimal: bare minimum setup

# Navigate to project
cd MyExpoApp

# Start development server
expo start

Lancer l'application

Réagir à l'IDC autochtone

# Run on Android
npx react-native run-android

# Run on iOS (macOS only)
npx react-native run-ios

# Run on specific device
npx react-native run-ios --device "iPhone 12"

# Run on specific simulator
npx react-native run-ios --simulator="iPhone 12 Pro Max"

# Start Metro bundler separately
npx react-native start

# Reset cache
npx react-native start --reset-cache

Exposition

# Start development server
expo start

# Run on Android device/emulator
expo start --android

# Run on iOS device/simulator
expo start --ios

# Run in web browser
expo start --web

# Run in tunnel mode (for physical devices)
expo start --tunnel

Commencer

Structure de base de l'application

// App.js
import React from 'react';
import {
  SafeAreaView,
  ScrollView,
  StatusBar,
  StyleSheet,
  Text,
  View,
} from 'react-native';

const App = () => {
  return (
    <SafeAreaView style={styles.container}>
      <StatusBar barStyle="dark-content" />
      <ScrollView contentInsetAdjustmentBehavior="automatic">
        <View style={styles.body}>
          <Text style={styles.title}>Welcome to React Native!</Text>
          <Text style={styles.subtitle}>
            Edit App.js to change this screen and then come back to see your edits.
          </Text>
        </View>
      </ScrollView>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
  },
  body: {
    padding: 20,
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    textAlign: 'center',
    marginBottom: 20,
  },
  subtitle: {
    fontSize: 16,
    textAlign: 'center',
    color: '#666',
  },
});

export default App;

Configuration du script de type

# Add TypeScript to existing project
npm install --save-dev typescript @types/react @types/react-native

# Create tsconfig.json
npx tsc --init

# Rename App.js to App.tsx
mv App.js App.tsx
// tsconfig.json
{
  "compilerOptions": {
    "target": "es2017",
    "lib": ["es2017", "es7", "es6"],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-native"
  },
  "include": [
    "src/**/*",
    "App.tsx"
  ],
  "exclude": [
    "node_modules"
  ]
}

Structure du projet

MyApp/
├── android/                 # Android-specific code
├── ios/                     # iOS-specific code
├── src/                     # Source code
│   ├── components/          # Reusable components
│   ├── screens/            # Screen components
│   ├── navigation/         # Navigation configuration
│   ├── services/           # API services
│   ├── utils/              # Utility functions
│   ├── hooks/              # Custom hooks
│   ├── context/            # React context
│   └── types/              # TypeScript types
├── __tests__/              # Test files
├── App.js                  # Main app component
├── index.js                # App entry point
├── package.json            # Dependencies
└── metro.config.js         # Metro bundler config

Composantes de base

Composantes de base

import React, { useState } from 'react';
import {
  View,
  Text,
  TextInput,
  TouchableOpacity,
  Image,
  ScrollView,
  FlatList,
  Alert,
} from 'react-native';

const BasicComponents = () => {
  const [text, setText] = useState('');
  const [items] = useState([
    { id: '1', title: 'Item 1' },
    { id: '2', title: 'Item 2' },
    { id: '3', title: 'Item 3' },
  ]);

  const handlePress = () => {
    Alert.alert('Button Pressed', `You entered: ${text}`);
  };

  const renderItem = ({ item }) => (
    <View style={styles.listItem}>
      <Text>{item.title}</Text>
    </View>
  );

  return (
    <ScrollView style={styles.container}>
      {/* Text Component */}
      <Text style={styles.heading}>React Native Components</Text>

      {/* TextInput Component */}
      <TextInput
        style={styles.input}
        placeholder="Enter text here"
        value={text}
        onChangeText={setText}
        multiline={false}
        autoCapitalize="none"
        autoCorrect={false}
      />

      {/* TouchableOpacity Component */}
      <TouchableOpacity style={styles.button} onPress={handlePress}>
        <Text style={styles.buttonText}>Press Me</Text>
      </TouchableOpacity>

      {/* Image Component */}
      <Image
        source={{ uri: 'https://reactnative.dev/img/tiny_logo.png' }}
        style={styles.image}
        resizeMode="contain"
      />

      {/* Local Image */}
      <Image
        source={require('./assets/logo.png')}
        style={styles.image}
      />

      {/* FlatList Component */}
      <FlatList
        data={items}
        renderItem={renderItem}
        keyExtractor={item => item.id}
        style={styles.list}
      />
    </ScrollView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
  },
  heading: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
  },
  input: {
    borderWidth: 1,
    borderColor: '#ddd',
    padding: 10,
    marginBottom: 20,
    borderRadius: 5,
  },
  button: {
    backgroundColor: '#007AFF',
    padding: 15,
    borderRadius: 5,
    alignItems: 'center',
    marginBottom: 20,
  },
  buttonText: {
    color: 'white',
    fontWeight: 'bold',
  },
  image: {
    width: 100,
    height: 100,
    marginBottom: 20,
  },
  list: {
    maxHeight: 200,
  },
  listItem: {
    padding: 10,
    borderBottomWidth: 1,
    borderBottomColor: '#eee',
  },
});

Composants avancés

import React, { useState, useRef } from 'react';
import {
  View,
  Text,
  Modal,
  Switch,
  Slider,
  Picker,
  ActivityIndicator,
  RefreshControl,
  Animated,
  PanGestureHandler,
} from 'react-native';

const AdvancedComponents = () => {
  const [modalVisible, setModalVisible] = useState(false);
  const [switchValue, setSwitchValue] = useState(false);
  const [sliderValue, setSliderValue] = useState(50);
  const [pickerValue, setPickerValue] = useState('option1');
  const [refreshing, setRefreshing] = useState(false);
  const animatedValue = useRef(new Animated.Value(0)).current;

  const onRefresh = () => {
    setRefreshing(true);
    setTimeout(() => setRefreshing(false), 2000);
  };

  const startAnimation = () => {
    Animated.timing(animatedValue, {
      toValue: 1,
      duration: 1000,
      useNativeDriver: true,
    }).start();
  };

  return (
    <ScrollView
      style={styles.container}
      refreshControl={
        <RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
      }
    >
      {/* Modal Component */}
      <TouchableOpacity
        style={styles.button}
        onPress={() => setModalVisible(true)}
      >
        <Text style={styles.buttonText}>Show Modal</Text>
      </TouchableOpacity>

      <Modal
        animationType="slide"
        transparent={true}
        visible={modalVisible}
        onRequestClose={() => setModalVisible(false)}
      >
        <View style={styles.modalContainer}>
          <View style={styles.modalContent}>
            <Text style={styles.modalText}>This is a modal!</Text>
            <TouchableOpacity
              style={styles.closeButton}
              onPress={() => setModalVisible(false)}
            >
              <Text style={styles.buttonText}>Close</Text>
            </TouchableOpacity>
          </View>
        </View>
      </Modal>

      {/* Switch Component */}
      <View style={styles.row}>
        <Text>Enable notifications:</Text>
        <Switch
          value={switchValue}
          onValueChange={setSwitchValue}
          trackColor={{ false: '#767577', true: '#81b0ff' }}
          thumbColor={switchValue ? '#f5dd4b' : '#f4f3f4'}
        />
      </View>

      {/* Slider Component */}
      <View style={styles.sliderContainer}>
        <Text>Value: {Math.round(sliderValue)}</Text>
        <Slider
          style={styles.slider}
          minimumValue={0}
          maximumValue={100}
          value={sliderValue}
          onValueChange={setSliderValue}
          minimumTrackTintColor="#1fb28a"
          maximumTrackTintColor="#d3d3d3"
          thumbStyle={{ backgroundColor: '#1fb28a' }}
        />
      </View>

      {/* Picker Component */}
      <View style={styles.pickerContainer}>
        <Text>Select an option:</Text>
        <Picker
          selectedValue={pickerValue}
          style={styles.picker}
          onValueChange={setPickerValue}
        >
          <Picker.Item label="Option 1" value="option1" />
          <Picker.Item label="Option 2" value="option2" />
          <Picker.Item label="Option 3" value="option3" />
        </Picker>
      </View>

      {/* Activity Indicator */}
      <View style={styles.loadingContainer}>
        <ActivityIndicator size="large" color="#0000ff" />
        <Text>Loading...</Text>
      </View>

      {/* Animated Component */}
      <TouchableOpacity style={styles.button} onPress={startAnimation}>
        <Text style={styles.buttonText}>Start Animation</Text>
      </TouchableOpacity>

      <Animated.View
        style={[
          styles.animatedBox,
          {
            opacity: animatedValue,
            transform: [
              {
                translateY: animatedValue.interpolate({
                  inputRange: [0, 1],
                  outputRange: [0, 100],
                }),
              },
            ],
          },
        ]}
      >
        <Text>Animated Box</Text>
      </Animated.View>
    </ScrollView>
  );
};

Composants personnalisés

// components/CustomButton.js
import React from 'react';
import { TouchableOpacity, Text, StyleSheet } from 'react-native';

const CustomButton = ({ 
  title, 
  onPress, 
  style, 
  textStyle, 
  disabled = false,
  variant = 'primary' 
}) => {
  const buttonStyle = [
    styles.button,
    styles[variant],
    disabled && styles.disabled,
    style,
  ];

  const buttonTextStyle = [
    styles.buttonText,
    styles[`${variant}Text`],
    disabled && styles.disabledText,
    textStyle,
  ];

  return (
    <TouchableOpacity
      style={buttonStyle}
      onPress={onPress}
      disabled={disabled}
      activeOpacity={0.7}
    >
      <Text style={buttonTextStyle}>{title}</Text>
    </TouchableOpacity>
  );
};

const styles = StyleSheet.create({
  button: {
    paddingVertical: 12,
    paddingHorizontal: 24,
    borderRadius: 8,
    alignItems: 'center',
    justifyContent: 'center',
    minHeight: 44,
  },
  buttonText: {
    fontSize: 16,
    fontWeight: '600',
  },
  primary: {
    backgroundColor: '#007AFF',
  },
  primaryText: {
    color: '#FFFFFF',
  },
  secondary: {
    backgroundColor: '#F2F2F7',
    borderWidth: 1,
    borderColor: '#C7C7CC',
  },
  secondaryText: {
    color: '#007AFF',
  },
  danger: {
    backgroundColor: '#FF3B30',
  },
  dangerText: {
    color: '#FFFFFF',
  },
  disabled: {
    backgroundColor: '#C7C7CC',
  },
  disabledText: {
    color: '#8E8E93',
  },
});

export default CustomButton;

// Usage
import CustomButton from './components/CustomButton';

const App = () => {
  return (
    <View style={styles.container}>
      <CustomButton
        title="Primary Button"
        onPress={() => console.log('Primary pressed')}
        variant="primary"
      />

      <CustomButton
        title="Secondary Button"
        onPress={() => console.log('Secondary pressed')}
        variant="secondary"
        style={{ marginTop: 10 }}
      />

      <CustomButton
        title="Disabled Button"
        onPress={() => console.log('This won\'t fire')}
        disabled={true}
        style={{ marginTop: 10 }}
      />
    </View>
  );
};

Configuration de la navigation de réaction

# Install React Navigation
npm install @react-navigation/native

# Install dependencies
npm install react-native-screens react-native-safe-area-context

# For iOS, install pods
cd ios && pod install && cd ..

# Install navigators
npm install @react-navigation/stack
npm install @react-navigation/bottom-tabs
npm install @react-navigation/drawer
npm install @react-navigation/material-top-tabs

# Install gesture handler (for stack navigator)
npm install react-native-gesture-handler

# For Android, add to MainActivity.java
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
// navigation/AppNavigator.js
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';

import HomeScreen from '../screens/HomeScreen';
import DetailsScreen from '../screens/DetailsScreen';
import ProfileScreen from '../screens/ProfileScreen';

const Stack = createStackNavigator();

const AppNavigator = () => {
  return (
    <NavigationContainer>
      <Stack.Navigator
        initialRouteName="Home"
        screenOptions={{
          headerStyle: {
            backgroundColor: '#007AFF',
          },
          headerTintColor: '#fff',
          headerTitleStyle: {
            fontWeight: 'bold',
          },
        }}
      >
        <Stack.Screen 
          name="Home" 
          component={HomeScreen}
          options={{ title: 'My Home' }}
        />
        <Stack.Screen 
          name="Details" 
          component={DetailsScreen}
          options={({ route }) => ({ title: route.params.title })}
        />
        <Stack.Screen 
          name="Profile" 
          component={ProfileScreen}
          options={{
            headerRight: () => (
              <Button
                onPress={() => alert('This is a button!')}
                title="Info"
                color="#fff"
              />
            ),
          }}
        />
      </Stack.Navigator>
    </NavigationContainer>
  );
};

export default AppNavigator;
// navigation/TabNavigator.js
import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Ionicons } from '@expo/vector-icons';

import HomeScreen from '../screens/HomeScreen';
import SearchScreen from '../screens/SearchScreen';
import ProfileScreen from '../screens/ProfileScreen';

const Tab = createBottomTabNavigator();

const TabNavigator = () => {
  return (
    <Tab.Navigator
      screenOptions={({ route }) => ({
        tabBarIcon: ({ focused, color, size }) => {
          let iconName;

          if (route.name === 'Home') {
            iconName = focused ? 'home' : 'home-outline';
          } else if (route.name === 'Search') {
            iconName = focused ? 'search' : 'search-outline';
          } else if (route.name === 'Profile') {
            iconName = focused ? 'person' : 'person-outline';
          }

          return <Ionicons name={iconName} size={size} color={color} />;
        },
        tabBarActiveTintColor: '#007AFF',
        tabBarInactiveTintColor: 'gray',
      })}
    >
      <Tab.Screen name="Home" component={HomeScreen} />
      <Tab.Screen name="Search" component={SearchScreen} />
      <Tab.Screen name="Profile" component={ProfileScreen} />
    </Tab.Navigator>
  );
};

export default TabNavigator;

Crochets de navigation

// screens/HomeScreen.js
import React from 'react';
import { View, Text, Button } from 'react-native';
import { useNavigation, useRoute, useFocusEffect } from '@react-navigation/native';

const HomeScreen = () => {
  const navigation = useNavigation();
  const route = useRoute();

  // Execute code when screen comes into focus
  useFocusEffect(
    React.useCallback(() => {
      console.log('Screen is focused');

      return () => {
        console.log('Screen is unfocused');
      };
    }, [])
  );

  const navigateToDetails = () => {
    navigation.navigate('Details', {
      itemId: 86,
      title: 'Details Screen',
      otherParam: 'anything you want here',
    });
  };

  const goBack = () => {
    navigation.goBack();
  };

  const resetNavigation = () => {
    navigation.reset({
      index: 0,
      routes: [{ name: 'Home' }],
    });
  };

  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Home Screen</Text>
      <Button title="Go to Details" onPress={navigateToDetails} />
      <Button title="Go Back" onPress={goBack} />
      <Button title="Reset Navigation" onPress={resetNavigation} />
    </View>
  );
};

export default HomeScreen;

// screens/DetailsScreen.js
import React from 'react';
import { View, Text, Button } from 'react-native';

const DetailsScreen = ({ route, navigation }) => {
  const { itemId, title, otherParam } = route.params;

  React.useEffect(() => {
    // Update header title
    navigation.setOptions({ title: title });
  }, [navigation, title]);

  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Details Screen</Text>
      <Text>Item ID: {itemId}</Text>
      <Text>Other Param: {otherParam}</Text>
      <Button
        title="Update Title"
        onPress={() => navigation.setOptions({ title: 'Updated!' })}
      />
      <Button
        title="Go to Details... again"
        onPress={() =>
          navigation.push('Details', {
            itemId: Math.floor(Math.random() * 100),
            title: 'New Details',
          })
        }
      />
      <Button title="Go to Home" onPress={() => navigation.navigate('Home')} />
      <Button title="Go back" onPress={() => navigation.goBack()} />
    </View>
  );
};

export default DetailsScreen;
// navigation/DrawerNavigator.js
import React from 'react';
import { createDrawerNavigator } from '@react-navigation/drawer';
import { View, Text, StyleSheet } from 'react-native';

import HomeScreen from '../screens/HomeScreen';
import ProfileScreen from '../screens/ProfileScreen';
import SettingsScreen from '../screens/SettingsScreen';

const Drawer = createDrawerNavigator();

// Custom drawer content
const CustomDrawerContent = (props) => {
  return (
    <View style={styles.drawerContainer}>
      <View style={styles.drawerHeader}>
        <Text style={styles.drawerHeaderText}>My App</Text>
      </View>
      <DrawerContentScrollView {...props}>
        <DrawerItemList {...props} />
      </DrawerContentScrollView>
    </View>
  );
};

const DrawerNavigator = () => {
  return (
    <Drawer.Navigator
      drawerContent={(props) => <CustomDrawerContent {...props} />}
      screenOptions={{
        drawerStyle: {
          backgroundColor: '#f6f6f6',
          width: 240,
        },
        drawerActiveTintColor: '#007AFF',
        drawerInactiveTintColor: '#666',
      }}
    >
      <Drawer.Screen 
        name="Home" 
        component={HomeScreen}
        options={{
          drawerIcon: ({ color }) => (
            <Ionicons name="home-outline" size={22} color={color} />
          ),
        }}
      />
      <Drawer.Screen 
        name="Profile" 
        component={ProfileScreen}
        options={{
          drawerIcon: ({ color }) => (
            <Ionicons name="person-outline" size={22} color={color} />
          ),
        }}
      />
      <Drawer.Screen 
        name="Settings" 
        component={SettingsScreen}
        options={{
          drawerIcon: ({ color }) => (
            <Ionicons name="settings-outline" size={22} color={color} />
          ),
        }}
      />
    </Drawer.Navigator>
  );
};

const styles = StyleSheet.create({
  drawerContainer: {
    flex: 1,
  },
  drawerHeader: {
    height: 100,
    backgroundColor: '#007AFF',
    justifyContent: 'center',
    alignItems: 'center',
  },
  drawerHeaderText: {
    color: 'white',
    fontSize: 18,
    fontWeight: 'bold',
  },
});

export default DrawerNavigator;

Administration de l ' État

Crochets de réaction

// hooks/useCounter.js
import { useState, useCallback } from 'react';

const useCounter = (initialValue = 0) => {
  const [count, setCount] = useState(initialValue);

  const increment = useCallback(() => {
    setCount(prev => prev + 1);
  }, []);

  const decrement = useCallback(() => {
    setCount(prev => prev - 1);
  }, []);

  const reset = useCallback(() => {
    setCount(initialValue);
  }, [initialValue]);

  return { count, increment, decrement, reset };
};

export default useCounter;

// Usage in component
import React from 'react';
import { View, Text, Button } from 'react-native';
import useCounter from '../hooks/useCounter';

const CounterScreen = () => {
  const { count, increment, decrement, reset } = useCounter(0);

  return (
    <View style={styles.container}>
      <Text style={styles.countText}>Count: {count}</Text>
      <Button title="Increment" onPress={increment} />
      <Button title="Decrement" onPress={decrement} />
      <Button title="Reset" onPress={reset} />
    </View>
  );
};

API contextuelle

// context/AuthContext.js
import React, { createContext, useContext, useReducer, useEffect } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';

const AuthContext = createContext();

const authReducer = (state, action) => {
  switch (action.type) {
    case 'RESTORE_TOKEN':
      return {
        ...state,
        userToken: action.token,
        isLoading: false,
      };
    case 'SIGN_IN':
      return {
        ...state,
        isSignout: false,
        userToken: action.token,
      };
    case 'SIGN_OUT':
      return {
        ...state,
        isSignout: true,
        userToken: null,
      };
    default:
      return state;
  }
};

export const AuthProvider = ({ children }) => {
  const [state, dispatch] = useReducer(authReducer, {
    isLoading: true,
    isSignout: false,
    userToken: null,
  });

  useEffect(() => {
    const bootstrapAsync = async () => {
      let userToken;

      try {
        userToken = await AsyncStorage.getItem('userToken');
      } catch (e) {
        // Restoring token failed
      }

      dispatch({ type: 'RESTORE_TOKEN', token: userToken });
    };

    bootstrapAsync();
  }, []);

  const authContext = {
    signIn: async (data) => {
      // Simulate API call
      const userToken = 'dummy-auth-token';
      await AsyncStorage.setItem('userToken', userToken);
      dispatch({ type: 'SIGN_IN', token: userToken });
    },
    signOut: async () => {
      await AsyncStorage.removeItem('userToken');
      dispatch({ type: 'SIGN_OUT' });
    },
    signUp: async (data) => {
      // Simulate API call
      const userToken = 'dummy-auth-token';
      await AsyncStorage.setItem('userToken', userToken);
      dispatch({ type: 'SIGN_IN', token: userToken });
    },
  };

  return (
    <AuthContext.Provider value={{ ...state, ...authContext }}>
      {children}
    </AuthContext.Provider>
  );
};

export const useAuth = () => {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth must be used within an AuthProvider');
  }
  return context;
};

// Usage in App.js
import React from 'react';
import { AuthProvider } from './context/AuthContext';
import AppNavigator from './navigation/AppNavigator';

const App = () => {
  return (
    <AuthProvider>
      <AppNavigator />
    </AuthProvider>
  );
};

export default App;

Redux Configuration

# Install Redux
npm install redux react-redux @reduxjs/toolkit
// store/store.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';
import authReducer from './authSlice';

export const store = configureStore({
  reducer: {
    counter: counterReducer,
    auth: authReducer,
  },
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

// store/counterSlice.js
import { createSlice } from '@reduxjs/toolkit';

const initialState = {
  value: 0,
};

export const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    },
  },
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;

// Usage in component
import React from 'react';
import { View, Text, Button } from 'react-native';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, incrementByAmount } from '../store/counterSlice';

const CounterScreen = () => {
  const count = useSelector((state) => state.counter.value);
  const dispatch = useDispatch();

  return (
    <View style={styles.container}>
      <Text style={styles.countText}>Count: {count}</Text>
      <Button title="+" onPress={() => dispatch(increment())} />
      <Button title="-" onPress={() => dispatch(decrement())} />
      <Button title="+5" onPress={() => dispatch(incrementByAmount(5))} />
    </View>
  );
};

// App.js with Redux Provider
import React from 'react';
import { Provider } from 'react-redux';
import { store } from './store/store';
import AppNavigator from './navigation/AppNavigator';

const App = () => {
  return (
    <Provider store={store}>
      <AppNavigator />
    </Provider>
  );
};

export default App;

Style

Feuille de style

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';

const StylingExample = () => {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Styling in React Native</Text>
      <View style={styles.box}>
        <Text style={styles.boxText}>Styled Box</Text>
      </View>
      <View style={[styles.box, styles.redBox]}>
        <Text style={[styles.boxText, styles.whiteText]}>Red Box</Text>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: '#f5f5f5',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    textAlign: 'center',
    marginBottom: 20,
    color: '#333',
  },
  box: {
    width: 200,
    height: 100,
    backgroundColor: '#007AFF',
    justifyContent: 'center',
    alignItems: 'center',
    marginBottom: 20,
    borderRadius: 10,
    shadowColor: '#000',
    shadowOffset: {
      width: 0,
      height: 2,
    },
    shadowOpacity: 0.25,
    shadowRadius: 3.84,
    elevation: 5, // Android shadow
  },
  boxText: {
    color: 'white',
    fontSize: 16,
    fontWeight: '600',
  },
  redBox: {
    backgroundColor: '#FF3B30',
  },
  whiteText: {
    color: 'white',
  },
});

export default StylingExample;

Flexbox Mise en page

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';

const FlexboxExample = () => {
  return (
    <View style={styles.container}>
      {/* Flex Direction */}
      <View style={styles.section}>
        <Text style={styles.sectionTitle}>Flex Direction: Row</Text>
        <View style={[styles.flexContainer, styles.row]}>
          <View style={[styles.box, styles.box1]} />
          <View style={[styles.box, styles.box2]} />
          <View style={[styles.box, styles.box3]} />
        </View>
      </View>

      {/* Justify Content */}
      <View style={styles.section}>
        <Text style={styles.sectionTitle}>Justify Content: Space Between</Text>
        <View style={[styles.flexContainer, styles.spaceBetween]}>
          <View style={[styles.box, styles.box1]} />
          <View style={[styles.box, styles.box2]} />
          <View style={[styles.box, styles.box3]} />
        </View>
      </View>

      {/* Align Items */}
      <View style={styles.section}>
        <Text style={styles.sectionTitle}>Align Items: Center</Text>
        <View style={[styles.flexContainer, styles.alignCenter]}>
          <View style={[styles.box, styles.box1, { height: 30 }]} />
          <View style={[styles.box, styles.box2, { height: 50 }]} />
          <View style={[styles.box, styles.box3, { height: 40 }]} />
        </View>
      </View>

      {/* Flex */}
      <View style={styles.section}>
        <Text style={styles.sectionTitle}>Flex: 1, 2, 1</Text>
        <View style={[styles.flexContainer, styles.row]}>
          <View style={[styles.box, styles.box1, { flex: 1 }]} />
          <View style={[styles.box, styles.box2, { flex: 2 }]} />
          <View style={[styles.box, styles.box3, { flex: 1 }]} />
        </View>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: '#f5f5f5',
  },
  section: {
    marginBottom: 30,
  },
  sectionTitle: {
    fontSize: 16,
    fontWeight: 'bold',
    marginBottom: 10,
    color: '#333',
  },
  flexContainer: {
    height: 80,
    backgroundColor: '#e0e0e0',
    borderRadius: 5,
  },
  row: {
    flexDirection: 'row',
  },
  spaceBetween: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    paddingHorizontal: 10,
  },
  alignCenter: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
  },
  box: {
    width: 50,
    height: 50,
    borderRadius: 5,
  },
  box1: {
    backgroundColor: '#FF6B6B',
  },
  box2: {
    backgroundColor: '#4ECDC4',
  },
  box3: {
    backgroundColor: '#45B7D1',
  },
});

export default FlexboxExample;

Conception sensible

import React from 'react';
import { View, Text, StyleSheet, Dimensions } from 'react-native';

const { width, height } = Dimensions.get('window');

const ResponsiveExample = () => {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Responsive Design</Text>

      {/* Percentage-based width */}
      <View style={styles.percentageBox}>
        <Text style={styles.boxText}>50% Width</Text>
      </View>

      {/* Screen dimension-based */}
      <View style={styles.dimensionBox}>
        <Text style={styles.boxText}>Screen Width: {width}</Text>
      </View>

      {/* Aspect ratio */}
      <View style={styles.aspectRatioBox}>
        <Text style={styles.boxText}>Aspect Ratio 16:9</Text>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: '#f5f5f5',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    textAlign: 'center',
    marginBottom: 20,
  },
  percentageBox: {
    width: '50%',
    height: 80,
    backgroundColor: '#007AFF',
    justifyContent: 'center',
    alignItems: 'center',
    marginBottom: 20,
    borderRadius: 10,
  },
  dimensionBox: {
    width: width * 0.8,
    height: 80,
    backgroundColor: '#FF3B30',
    justifyContent: 'center',
    alignItems: 'center',
    marginBottom: 20,
    borderRadius: 10,
  },
  aspectRatioBox: {
    width: '100%',
    aspectRatio: 16 / 9,
    backgroundColor: '#34C759',
    justifyContent: 'center',
    alignItems: 'center',
    borderRadius: 10,
  },
  boxText: {
    color: 'white',
    fontSize: 16,
    fontWeight: '600',
    textAlign: 'center',
  },
});

export default ResponsiveExample;

Composants stylés

# Install styled-components
npm install styled-components
npm install --save-dev @types/styled-components-react-native
import React from 'react';
import styled from 'styled-components/native';

const Container = styled.View`
  flex: 1;
  padding: 20px;
  background-color: #f5f5f5;
`;

const Title = styled.Text`
  font-size: 24px;
  font-weight: bold;
  text-align: center;
  margin-bottom: 20px;
  color: #333;
`;

const StyledButton = styled.TouchableOpacity`
  background-color: ${props => props.primary ? '#007AFF' : '#FF3B30'};
  padding: 15px 30px;
  border-radius: 10px;
  margin-bottom: 10px;
  align-items: center;
`;

const ButtonText = styled.Text`
  color: white;
  font-size: 16px;
  font-weight: 600;
`;

const StyledComponentsExample = () => {
  return (
    <Container>
      <Title>Styled Components</Title>

      <StyledButton primary onPress={() => console.log('Primary pressed')}>
        <ButtonText>Primary Button</ButtonText>
      </StyledButton>

      <StyledButton onPress={() => console.log('Secondary pressed')}>
        <ButtonText>Secondary Button</ButtonText>
      </StyledButton>
    </Container>
  );
};

export default StyledComponentsExample;

API et services

Récupération de l'API

// services/apiService.js
const API_BASE_URL = 'https://jsonplaceholder.typicode.com';

class ApiService {
  static async get(endpoint) {
    try {
      const response = await fetch(`${API_BASE_URL}${endpoint}`);

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }

      return await response.json();
    } catch (error) {
      console.error('GET request failed:', error);
      throw error;
    }
  }

  static async post(endpoint, data) {
    try {
      const response = await fetch(`${API_BASE_URL}${endpoint}`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(data),
      });

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }

      return await response.json();
    } catch (error) {
      console.error('POST request failed:', error);
      throw error;
    }
  }

  static async put(endpoint, data) {
    try {
      const response = await fetch(`${API_BASE_URL}${endpoint}`, {
        method: 'PUT',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(data),
      });

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }

      return await response.json();
    } catch (error) {
      console.error('PUT request failed:', error);
      throw error;
    }
  }

  static async delete(endpoint) {
    try {
      const response = await fetch(`${API_BASE_URL}${endpoint}`, {
        method: 'DELETE',
      });

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }

      return response.status === 204 ? null : await response.json();
    } catch (error) {
      console.error('DELETE request failed:', error);
      throw error;
    }
  }
}

export default ApiService;

// Usage in component
import React, { useState, useEffect } from 'react';
import { View, Text, FlatList, ActivityIndicator } from 'react-native';
import ApiService from '../services/apiService';

const PostsScreen = () => {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetchPosts();
  }, []);

  const fetchPosts = async () => {
    try {
      setLoading(true);
      const data = await ApiService.get('/posts');
      setPosts(data);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  const renderPost = ({ item }) => (
    <View style={styles.postItem}>
      <Text style={styles.postTitle}>{item.title}</Text>
      <Text style={styles.postBody}>{item.body}</Text>
    </View>
  );

  if (loading) {
    return (
      <View style={styles.centered}>
        <ActivityIndicator size="large" color="#007AFF" />
      </View>
    );
  }

  if (error) {
    return (
      <View style={styles.centered}>
        <Text style={styles.errorText}>Error: {error}</Text>
      </View>
    );
  }

  return (
    <FlatList
      data={posts}
      renderItem={renderPost}
      keyExtractor={item => item.id.toString()}
      style={styles.container}
    />
  );
};

Intégration Axios

# Install Axios
npm install axios
// services/httpClient.js
import axios from 'axios';
import AsyncStorage from '@react-native-async-storage/async-storage';

const httpClient = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json',
  },
});

// Request interceptor
httpClient.interceptors.request.use(
  async (config) => {
    const token = await AsyncStorage.getItem('authToken');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

// Response interceptor
httpClient.interceptors.response.use(
  (response) => {
    return response;
  },
  async (error) => {
    if (error.response?.status === 401) {
      // Handle unauthorized access
      await AsyncStorage.removeItem('authToken');
      // Navigate to login screen
    }
    return Promise.reject(error);
  }
);

export default httpClient;

// services/userService.js
import httpClient from './httpClient';

export const userService = {
  getUsers: () => httpClient.get('/users'),
  getUser: (id) => httpClient.get(`/users/${id}`),
  createUser: (userData) => httpClient.post('/users', userData),
  updateUser: (id, userData) => httpClient.put(`/users/${id}`, userData),
  deleteUser: (id) => httpClient.delete(`/users/${id}`),
};

AsyncStorage

# Install AsyncStorage
npm install @react-native-async-storage/async-storage

# For iOS
cd ios && pod install && cd ..
// utils/storage.js
import AsyncStorage from '@react-native-async-storage/async-storage';

export const storage = {
  // Store data
  setItem: async (key, value) => {
    try {
      const jsonValue = JSON.stringify(value);
      await AsyncStorage.setItem(key, jsonValue);
    } catch (error) {
      console.error('Error storing data:', error);
    }
  },

  // Retrieve data
  getItem: async (key) => {
    try {
      const jsonValue = await AsyncStorage.getItem(key);
      return jsonValue != null ? JSON.parse(jsonValue) : null;
    } catch (error) {
      console.error('Error retrieving data:', error);
      return null;
    }
  },

  // Remove data
  removeItem: async (key) => {
    try {
      await AsyncStorage.removeItem(key);
    } catch (error) {
      console.error('Error removing data:', error);
    }
  },

  // Clear all data
  clear: async () => {
    try {
      await AsyncStorage.clear();
    } catch (error) {
      console.error('Error clearing storage:', error);
    }
  },

  // Get all keys
  getAllKeys: async () => {
    try {
      return await AsyncStorage.getAllKeys();
    } catch (error) {
      console.error('Error getting keys:', error);
      return [];
    }
  },
};

// Usage in component
import React, { useState, useEffect } from 'react';
import { View, Text, TextInput, Button } from 'react-native';
import { storage } from '../utils/storage';

const StorageExample = () => {
  const [name, setName] = useState('');
  const [savedName, setSavedName] = useState('');

  useEffect(() => {
    loadSavedName();
  }, []);

  const loadSavedName = async () => {
    const saved = await storage.getItem('userName');
    if (saved) {
      setSavedName(saved);
    }
  };

  const saveName = async () => {
    await storage.setItem('userName', name);
    setSavedName(name);
    setName('');
  };

  const clearName = async () => {
    await storage.removeItem('userName');
    setSavedName('');
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>AsyncStorage Example</Text>

      <TextInput
        style={styles.input}
        placeholder="Enter your name"
        value={name}
        onChangeText={setName}
      />

      <Button title="Save Name" onPress={saveName} />
      <Button title="Clear Name" onPress={clearName} />

      {savedName ? (
        <Text style={styles.savedText}>Saved Name: {savedName}</Text>
      ) : (
        <Text style={styles.noDataText}>No saved name</Text>
      )}
    </View>
  );
};

Modules natifs

Accès aux fonctionnalités de l'appareil

// Camera and Image Picker
import { launchImageLibrary, launchCamera } from 'react-native-image-picker';

const ImagePickerExample = () => {
  const [imageUri, setImageUri] = useState(null);

  const selectImage = () => {
    const options = {
      mediaType: 'photo',
      includeBase64: false,
      maxHeight: 2000,
      maxWidth: 2000,
    };

    launchImageLibrary(options, (response) => {
      if (response.didCancel || response.error) {
        console.log('User cancelled or error');
      } else {
        setImageUri(response.assets[0].uri);
      }
    });
  };

  const takePhoto = () => {
    const options = {
      mediaType: 'photo',
      includeBase64: false,
      maxHeight: 2000,
      maxWidth: 2000,
    };

    launchCamera(options, (response) => {
      if (response.didCancel || response.error) {
        console.log('User cancelled or error');
      } else {
        setImageUri(response.assets[0].uri);
      }
    });
  };

  return (
    <View style={styles.container}>
      <Button title="Select from Gallery" onPress={selectImage} />
      <Button title="Take Photo" onPress={takePhoto} />

      {imageUri && (
        <Image source={{ uri: imageUri }} style={styles.image} />
      )}
    </View>
  );
};

// Geolocation
import Geolocation from '@react-native-community/geolocation';

const LocationExample = () => {
  const [location, setLocation] = useState(null);

  const getCurrentLocation = () => {
    Geolocation.getCurrentPosition(
      (position) => {
        setLocation({
          latitude: position.coords.latitude,
          longitude: position.coords.longitude,
        });
      },
      (error) => {
        console.error('Error getting location:', error);
      },
      { enableHighAccuracy: true, timeout: 15000, maximumAge: 10000 }
    );
  };

  return (
    <View style={styles.container}>
      <Button title="Get Current Location" onPress={getCurrentLocation} />

      {location && (
        <Text>
          Latitude: {location.latitude}, Longitude: {location.longitude}
        </Text>
      )}
    </View>
  );
};

// Permissions
import { PermissionsAndroid, Platform } from 'react-native';

const requestCameraPermission = async () => {
  if (Platform.OS === 'android') {
    try {
      const granted = await PermissionsAndroid.request(
        PermissionsAndroid.PERMISSIONS.CAMERA,
        {
          title: 'Camera Permission',
          message: 'App needs camera permission to take photos',
          buttonNeutral: 'Ask Me Later',
          buttonNegative: 'Cancel',
          buttonPositive: 'OK',
        }
      );

      return granted === PermissionsAndroid.RESULTS.GRANTED;
    } catch (err) {
      console.warn(err);
      return false;
    }
  }
  return true; // iOS permissions handled automatically
};

Création de modules natifs personnalisés

// Android: android/app/src/main/java/com/yourapp/CustomModule.java
package com.yourapp;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Promise;

public class CustomModule extends ReactContextBaseJavaModule {
    CustomModule(ReactApplicationContext context) {
        super(context);
    }

    @Override
    public String getName() {
        return "CustomModule";
    }

    @ReactMethod
    public void multiply(int a, int b, Promise promise) {
        try {
            int result = a * b;
            promise.resolve(result);
        } catch (Exception e) {
            promise.reject("CALCULATION_ERROR", e);
        }
    }
}

// iOS: ios/CustomModule.h
#import <React/RCTBridgeModule.h>

@interface CustomModule : NSObject <RCTBridgeModule>
@end

// iOS: ios/CustomModule.m
#import "CustomModule.h"

@implementation CustomModule

RCT_EXPORT_MODULE();

RCT_EXPORT_METHOD(multiply:(int)a withB:(int)b
                 withResolver:(RCTPromiseResolveBlock)resolve
                 withRejecter:(RCTPromiseRejectBlock)reject)
{
  NSNumber *result = @(a * b);
  resolve(result);
}

@end

// JavaScript usage
import { NativeModules } from 'react-native';

const { CustomModule } = NativeModules;

const useCustomModule = () => {
  const multiply = async (a, b) => {
    try {
      const result = await CustomModule.multiply(a, b);
      return result;
    } catch (error) {
      console.error('Error in custom module:', error);
      throw error;
    }
  };

  return { multiply };
};

Rendement

Techniques d'optimisation

// React.memo for component optimization
import React, { memo } from 'react';

const ExpensiveComponent = memo(({ data, onPress }) => {
  console.log('ExpensiveComponent rendered');

  return (
    <View style={styles.container}>
      <Text>{data.title}</Text>
      <Button title="Press me" onPress={onPress} />
    </View>
  );
});

// useCallback for function memoization
import React, { useState, useCallback } from 'react';

const ParentComponent = () => {
  const [count, setCount] = useState(0);
  const [data] = useState({ title: 'Static data' });

  const handlePress = useCallback(() => {
    console.log('Button pressed');
  }, []);

  return (
    <View>
      <Text>Count: {count}</Text>
      <Button title="Increment" onPress={() => setCount(count + 1)} />
      <ExpensiveComponent data={data} onPress={handlePress} />
    </View>
  );
};

// useMemo for expensive calculations
import React, { useMemo } from 'react';

const ExpensiveCalculation = ({ items }) => {
  const expensiveValue = useMemo(() => {
    console.log('Calculating expensive value...');
    return items.reduce((sum, item) => sum + item.value, 0);
  }, [items]);

  return <Text>Total: {expensiveValue}</Text>;
};

Optimisation FlatList

import React, { useState, useCallback } from 'react';
import { FlatList, View, Text, StyleSheet } from 'react-native';

const OptimizedFlatList = () => {
  const [data] = useState(
    Array.from({ length: 1000 }, (_, index) => ({
      id: index.toString(),
      title: `Item ${index}`,
      subtitle: `Subtitle ${index}`,
    }))
  );

  const renderItem = useCallback(({ item }) => (
    <View style={styles.item}>
      <Text style={styles.title}>{item.title}</Text>
      <Text style={styles.subtitle}>{item.subtitle}</Text>
    </View>
  ), []);

  const keyExtractor = useCallback((item) => item.id, []);

  const getItemLayout = useCallback(
    (data, index) => ({
      length: 80, // Item height
      offset: 80 * index,
      index,
    }),
    []
  );

  return (
    <FlatList
      data={data}
      renderItem={renderItem}
      keyExtractor={keyExtractor}
      getItemLayout={getItemLayout}
      initialNumToRender={10}
      maxToRenderPerBatch={10}
      windowSize={10}
      removeClippedSubviews={true}
      updateCellsBatchingPeriod={50}
    />
  );
};

const styles = StyleSheet.create({
  item: {
    height: 80,
    padding: 16,
    borderBottomWidth: 1,
    borderBottomColor: '#eee',
  },
  title: {
    fontSize: 16,
    fontWeight: 'bold',
  },
  subtitle: {
    fontSize: 14,
    color: '#666',
  },
});

Optimisation de l'image

import React from 'react';
import { Image, View } from 'react-native';
import FastImage from 'react-native-fast-image';

const ImageOptimization = () => {
  return (
    <View>
      {/* Standard Image with optimization */}
      <Image
        source={{ uri: 'https://example.com/image.jpg' }}
        style={styles.image}
        resizeMode="cover"
        defaultSource={require('./assets/placeholder.png')}
        loadingIndicatorSource={require('./assets/loading.png')}
      />

      {/* FastImage for better performance */}
      <FastImage
        style={styles.image}
        source={{
          uri: 'https://example.com/image.jpg',
          priority: FastImage.priority.normal,
          cache: FastImage.cacheControl.immutable,
        }}
        resizeMode={FastImage.resizeMode.cover}
        onLoad={() => console.log('Image loaded')}
        onError={() => console.log('Image load error')}
      />
    </View>
  );
};

Optimisation de la taille des ensembles

// metro.config.js
const { getDefaultConfig } = require('metro-config');

module.exports = (async () => {
  const {
    resolver: { sourceExts, assetExts },
  } = await getDefaultConfig();

  return {
    transformer: {
      babelTransformerPath: require.resolve('react-native-svg-transformer'),
    },
    resolver: {
      assetExts: assetExts.filter(ext => ext !== 'svg'),
      sourceExts: [...sourceExts, 'svg'],
    },
    // Enable bundle splitting
    serializer: {
      createModuleIdFactory: function () {
        return function (path) {
          // Use shorter module IDs
          return path.substr(1).replace(/\//g, '_');
        };
      },
    },
  };
})();

// Dynamic imports for code splitting
const LazyComponent = React.lazy(() => import('./LazyComponent'));

const App = () => {
  return (
    <Suspense fallback={<ActivityIndicator />}>
      <LazyComponent />
    </Suspense>
  );
};

Essais

Jest Setup

# Jest is included with React Native by default
# Install additional testing utilities
npm install --save-dev @testing-library/react-native
npm install --save-dev @testing-library/jest-native
// __tests__/App.test.js
import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react-native';
import '@testing-library/jest-native/extend-expect';
import App from '../App';

describe('App', () => {
  it('renders correctly', () => {
    const { getByText } = render(<App />);
    expect(getByText('Welcome to React Native!')).toBeTruthy();
  });

  it('handles button press', async () => {
    const { getByText, getByTestId } = render(<App />);
    const button = getByTestId('test-button');

    fireEvent.press(button);

    await waitFor(() => {
      expect(getByText('Button pressed!')).toBeTruthy();
    });
  });
});

// Component testing
import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import CustomButton from '../components/CustomButton';

describe('CustomButton', () => {
  it('calls onPress when pressed', () => {
    const mockOnPress = jest.fn();
    const { getByText } = render(
      <CustomButton title="Test Button" onPress={mockOnPress} />
    );

    fireEvent.press(getByText('Test Button'));
    expect(mockOnPress).toHaveBeenCalledTimes(1);
  });

  it('is disabled when disabled prop is true', () => {
    const { getByText } = render(
      <CustomButton title="Test Button" disabled={true} />
    );

    const button = getByText('Test Button').parent;
    expect(button).toBeDisabled();
  });
});

Détox E2E Essais

# Install Detox
npm install --save-dev detox

# Initialize Detox
npx detox init

# Install Detox CLI
npm install -g detox-cli
// e2e/firstTest.e2e.js
describe('Example', () => {
  beforeAll(async () => {
    await device.launchApp();
  });

  beforeEach(async () => {
    await device.reloadReactNative();
  });

  it('should have welcome screen', async () => {
    await expect(element(by.id('welcome'))).toBeVisible();
  });

  it('should show hello screen after tap', async () => {
    await element(by.id('hello_button')).tap();
    await expect(element(by.text('Hello!!!'))).toBeVisible();
  });

  it('should show world screen after tap', async () => {
    await element(by.id('world_button')).tap();
    await expect(element(by.text('World!!!'))).toBeVisible();
  });
});

// .detoxrc.json
{
  "testRunner": "jest",
  "runnerConfig": "e2e/config.json",
  "configurations": {
    "ios.sim.debug": {
      "binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/YourApp.app",
      "build": "xcodebuild -workspace ios/YourApp.xcworkspace -scheme YourApp -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build",
      "type": "ios.simulator",
      "device": {
        "type": "iPhone 12"
      }
    },
    "android.emu.debug": {
      "binaryPath": "android/app/build/outputs/apk/debug/app-debug.apk",
      "build": "cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug && cd ..",
      "type": "android.emulator",
      "device": {
        "avdName": "Pixel_4_API_30"
      }
    }
  }
}

Fumer

// __mocks__/@react-native-async-storage/async-storage.js
export default {
  setItem: jest.fn(() => Promise.resolve()),
  getItem: jest.fn(() => Promise.resolve(null)),
  removeItem: jest.fn(() => Promise.resolve()),
  clear: jest.fn(() => Promise.resolve()),
  getAllKeys: jest.fn(() => Promise.resolve([])),
};

// __tests__/services/apiService.test.js
import ApiService from '../services/apiService';

// Mock fetch
global.fetch = jest.fn();

describe('ApiService', () => {
  beforeEach(() => {
    fetch.mockClear();
  });

  it('should fetch data successfully', async () => {
    const mockData = { id: 1, title: 'Test Post' };
    fetch.mockResolvedValueOnce({
      ok: true,
      json: async () => mockData,
    });

    const result = await ApiService.get('/posts/1');

    expect(fetch).toHaveBeenCalledWith(
      'https://jsonplaceholder.typicode.com/posts/1'
    );
    expect(result).toEqual(mockData);
  });

  it('should handle fetch errors', async () => {
    fetch.mockRejectedValueOnce(new Error('Network error'));

    await expect(ApiService.get('/posts/1')).rejects.toThrow('Network error');
  });
});

Déboguement

Réagir au débogueur natif

# Install React Native Debugger
# Download from: https://github.com/jhen0409/react-native-debugger

# Enable debugging in app
# Shake device or press Cmd+D (iOS) / Cmd+M (Android)
# Select "Debug JS Remotely"

Intégration Flipper

# Flipper is included by default in React Native 0.62+
# Download Flipper desktop app from: https://fbflipper.com/

# Install Flipper plugins
npm install --save-dev react-native-flipper
// utils/flipper.js
import { logger } from 'react-native-logs';

const defaultConfig = {
  severity: __DEV__ ? 'debug' : 'error',
  transport: __DEV__ ? logger.consoleTransport : logger.fileAsyncTransport,
  transportOptions: {
    colors: {
      info: 'blueBright',
      warn: 'yellowBright',
      error: 'redBright',
    },
  },
};

const log = logger.createLogger(defaultConfig);

export default log;

// Usage in components
import log from '../utils/flipper';

const MyComponent = () => {
  const handlePress = () => {
    log.debug('Button pressed');
    log.info('User interaction logged');
  };

  return (
    <Button title="Press me" onPress={handlePress} />
  );
};

Surveillance de la performance

// utils/performance.js
import { InteractionManager } from 'react-native';

export const measurePerformance = (name, fn) => {
  return (...args) => {
    const start = Date.now();

    const result = fn(...args);

    if (result instanceof Promise) {
      return result.finally(() => {
        const end = Date.now();
        console.log(`${name} took ${end - start}ms`);
      });
    } else {
      const end = Date.now();
      console.log(`${name} took ${end - start}ms`);
      return result;
    }
  };
};

export const runAfterInteractions = (fn) => {
  return InteractionManager.runAfterInteractions(fn);
};

// Usage
const expensiveOperation = measurePerformance('expensiveOperation', () => {
  // Expensive computation
  return heavyCalculation();
});

// Run after animations complete
runAfterInteractions(() => {
  // Heavy operation that shouldn't block UI
  processLargeDataSet();
});

Limites des erreurs

// components/ErrorBoundary.js
import React from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }

  componentDidCatch(error, errorInfo) {
    console.error('Error caught by boundary:', error, errorInfo);

    // Log to crash reporting service
    // crashlytics().recordError(error);
  }

  render() {
    if (this.state.hasError) {
      return (
        <View style={styles.container}>
          <Text style={styles.title}>Something went wrong</Text>
          <Text style={styles.error}>{this.state.error?.message}</Text>
          <Button
            title="Try again"
            onPress={() => this.setState({ hasError: false, error: null })}
          />
        </View>
      );
    }

    return this.props.children;
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
  title: {
    fontSize: 20,
    fontWeight: 'bold',
    marginBottom: 10,
  },
  error: {
    fontSize: 14,
    color: 'red',
    textAlign: 'center',
    marginBottom: 20,
  },
});

export default ErrorBoundary;

// Usage in App.js
import ErrorBoundary from './components/ErrorBoundary';

const App = () => {
  return (
    <ErrorBoundary>
      <AppNavigator />
    </ErrorBoundary>
  );
};

Déploiement

Déploiement Android

# Generate signed APK
cd android
./gradlew assembleRelease

# Generate AAB (Android App Bundle)
./gradlew bundleRelease

# Install release APK
adb install app/build/outputs/apk/release/app-release.apk
// android/app/build.gradle
android {
    ...
    signingConfigs {
        release {
            if (project.hasProperty('MYAPP_UPLOAD_STORE_FILE')) {
                storeFile file(MYAPP_UPLOAD_STORE_FILE)
                storePassword MYAPP_UPLOAD_STORE_PASSWORD
                keyAlias MYAPP_UPLOAD_KEY_ALIAS
                keyPassword MYAPP_UPLOAD_KEY_PASSWORD
            }
        }
    }
    buildTypes {
        release {
            ...
            signingConfig signingConfigs.release
        }
    }
}

// android/gradle.properties
MYAPP_UPLOAD_STORE_FILE=my-upload-key.keystore
MYAPP_UPLOAD_KEY_ALIAS=my-key-alias
MYAPP_UPLOAD_STORE_PASSWORD=*****
MYAPP_UPLOAD_KEY_PASSWORD=*****

Déploiement iOS

# Archive for distribution
xcodebuild -workspace ios/MyApp.xcworkspace \
           -scheme MyApp \
           -configuration Release \
           -archivePath ios/build/MyApp.xcarchive \
           archive

# Export IPA
xcodebuild -exportArchive \
           -archivePath ios/build/MyApp.xcarchive \
           -exportPath ios/build \
           -exportOptionsPlist ios/ExportOptions.plist

Automatisation par voie rapide

# Install Fastlane
sudo gem install fastlane

# Initialize Fastlane
cd ios && fastlane init
cd android && fastlane init
# ios/fastlane/Fastfile
default_platform(:ios)

platform :ios do
  desc "Build and upload to TestFlight"
  lane :beta do
    increment_build_number(xcodeproj: "MyApp.xcodeproj")
    build_app(workspace: "MyApp.xcworkspace", scheme: "MyApp")
    upload_to_testflight
  end

  desc "Build and upload to App Store"
  lane :release do
    increment_build_number(xcodeproj: "MyApp.xcodeproj")
    build_app(workspace: "MyApp.xcworkspace", scheme: "MyApp")
    upload_to_app_store
  end
end

# android/fastlane/Fastfile
default_platform(:android)

platform :android do
  desc "Build and upload to Play Console"
  lane :beta do
    gradle(task: "clean bundleRelease")
    upload_to_play_store(track: 'beta')
  end

  desc "Deploy to Play Store"
  lane :release do
    gradle(task: "clean bundleRelease")
    upload_to_play_store
  end
end

CodePush (mises à jour en direct)

# Install CodePush CLI
npm install -g code-push-cli

# Install CodePush SDK
npm install --save react-native-code-push

# Register app
code-push app add MyApp-iOS ios react-native
code-push app add MyApp-Android android react-native
// App.js with CodePush
import codePush from 'react-native-code-push';

const App = () => {
  return (
    <NavigationContainer>
      <AppNavigator />
    </NavigationContainer>
  );
};

const codePushOptions = {
  checkFrequency: codePush.CheckFrequency.ON_APP_RESUME,
  installMode: codePush.InstallMode.ON_NEXT_RESUME,
};

export default codePush(codePushOptions)(App);

// Release update
// code-push release-react MyApp-iOS ios
// code-push release-react MyApp-Android android

Code spécifique à la plate-forme

Module Plateforme

import { Platform } from 'react-native';

const PlatformExample = () => {
  return (
    <View style={styles.container}>
      <Text>Platform: {Platform.OS}</Text>
      <Text>Version: {Platform.Version}</Text>

      {Platform.OS === 'ios' && (
        <Text>This is iOS specific content</Text>
      )}

      {Platform.OS === 'android' && (
        <Text>This is Android specific content</Text>
      )}

      <View style={Platform.select({
        ios: styles.iosContainer,
        android: styles.androidContainer,
      })}>
        <Text>Platform specific styling</Text>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
  },
  iosContainer: {
    backgroundColor: '#f0f0f0',
    borderRadius: 10,
  },
  androidContainer: {
    backgroundColor: '#e0e0e0',
    elevation: 5,
  },
});

Fichiers spécifiques à la plateforme

// components/Button.ios.js
import React from 'react';
import { TouchableOpacity, Text } from 'react-native';

const Button = ({ title, onPress }) => (
  <TouchableOpacity style={styles.iosButton} onPress={onPress}>
    <Text style={styles.iosButtonText}>{title}</Text>
  </TouchableOpacity>
);

// components/Button.android.js
import React from 'react';
import { TouchableNativeFeedback, Text, View } from 'react-native';

const Button = ({ title, onPress }) => (
  <TouchableNativeFeedback onPress={onPress}>
    <View style={styles.androidButton}>
      <Text style={styles.androidButtonText}>{title}</Text>
    </View>
  </TouchableNativeFeedback>
);

// Usage (React Native automatically picks the right file)
import Button from './components/Button';

Gestion sûre des zones

import React from 'react';
import { SafeAreaView, StatusBar, Platform } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

const SafeAreaExample = () => {
  const insets = useSafeAreaInsets();

  return (
    <SafeAreaView style={styles.container}>
      <StatusBar
        barStyle={Platform.OS === 'ios' ? 'dark-content' : 'light-content'}
        backgroundColor={Platform.OS === 'android' ? '#007AFF' : undefined}
      />

      <View style={{
        paddingTop: insets.top,
        paddingBottom: insets.bottom,
        paddingLeft: insets.left,
        paddingRight: insets.right,
      }}>
        <Text>Content with safe area padding</Text>
      </View>
    </SafeAreaView>
  );
};

Bibliothèques de tiers

Bibliothèques populaires

# UI Libraries
npm install react-native-elements
npm install react-native-paper
npm install native-base

# Navigation
npm install @react-navigation/native
npm install @react-navigation/stack
npm install @react-navigation/bottom-tabs

# State Management
npm install redux react-redux @reduxjs/toolkit
npm install zustand
npm install recoil

# Networking
npm install axios
npm install react-query

# Storage
npm install @react-native-async-storage/async-storage
npm install react-native-mmkv

# Media
npm install react-native-image-picker
npm install react-native-video
npm install react-native-sound

# Maps
npm install react-native-maps

# Animations
npm install react-native-reanimated
npm install lottie-react-native

# Forms
npm install react-hook-form
npm install formik

# Utils
npm install react-native-device-info
npm install react-native-permissions
npm install react-native-keychain

Exemples d'intégration de la bibliothèque

// React Native Elements
import React from 'react';
import { Header, Button, Card } from 'react-native-elements';
import Icon from 'react-native-vector-icons/FontAwesome';

const ElementsExample = () => (
  <View>
    <Header
      centerComponent={{ text: 'MY TITLE', style: { color: '#fff' } }}
      rightComponent={{ icon: 'home', color: '#fff' }}
    />

    <Card title="CARD WITH DIVIDER">
      <Text>Card content goes here</Text>
      <Button
        icon={<Icon name="code" size={15} color="white" />}
        buttonStyle={{ borderRadius: 0, marginLeft: 0, marginRight: 0, marginBottom: 0 }}
        title="VIEW NOW"
      />
    </Card>
  </View>
);

// React Hook Form
import React from 'react';
import { useForm, Controller } from 'react-hook-form';
import { TextInput, Button, Text } from 'react-native';

const FormExample = () => {
  const { control, handleSubmit, formState: { errors } } = useForm();

  const onSubmit = (data) => {
    console.log(data);
  };

  return (
    <View>
      <Controller
        control={control}
        rules={{ required: true }}
        render={({ field: { onChange, onBlur, value } }) => (
          <TextInput
            placeholder="Email"
            onBlur={onBlur}
            onChangeText={onChange}
            value={value}
            style={styles.input}
          />
        )}
        name="email"
      />
      {errors.email && <Text>Email is required.</Text>}

      <Button title="Submit" onPress={handleSubmit(onSubmit)} />
    </View>
  );
};

// React Native Reanimated
import React from 'react';
import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withSpring,
} from 'react-native-reanimated';

const AnimatedExample = () => {
  const translateX = useSharedValue(0);

  const animatedStyle = useAnimatedStyle(() => {
    return {
      transform: [{ translateX: translateX.value }],
    };
  });

  const handlePress = () => {
    translateX.value = withSpring(translateX.value + 50);
  };

  return (
    <View>
      <Animated.View style={[styles.box, animatedStyle]} />
      <Button title="Move" onPress={handlePress} />
    </View>
  );
};

Meilleures pratiques

Code Organisation

src/
├── components/          # Reusable UI components
│   ├── common/         # Generic components
│   ├── forms/          # Form-specific components
│   └── navigation/     # Navigation components
├── screens/            # Screen components
│   ├── auth/          # Authentication screens
│   ├── main/          # Main app screens
│   └── settings/      # Settings screens
├── services/           # API and external services
├── utils/             # Utility functions
├── hooks/             # Custom React hooks
├── context/           # React context providers
├── navigation/        # Navigation configuration
├── assets/            # Images, fonts, etc.
├── constants/         # App constants
└── types/             # TypeScript type definitions

Meilleures pratiques en matière de rendement

// 1. Use FlatList for large lists
const LargeList = ({ data }) => (
  <FlatList
    data={data}
    renderItem={({ item }) => <ListItem item={item} />}
    keyExtractor={item => item.id}
    getItemLayout={(data, index) => ({
      length: ITEM_HEIGHT,
      offset: ITEM_HEIGHT * index,
      index,
    })}
    removeClippedSubviews={true}
    maxToRenderPerBatch={10}
    windowSize={10}
  />
);

// 2. Optimize images
const OptimizedImage = ({ uri }) => (
  <Image
    source={{ uri }}
    style={styles.image}
    resizeMode="cover"
    defaultSource={require('./placeholder.png')}
  />
);

// 3. Use InteractionManager for heavy operations
import { InteractionManager } from 'react-native';

const HeavyOperationComponent = () => {
  useEffect(() => {
    InteractionManager.runAfterInteractions(() => {
      // Heavy operation that shouldn't block UI
      performHeavyOperation();
    });
  }, []);

  return <View>...</View>;
};

// 4. Minimize bridge communication
const MinimizeBridge = () => {
  // Bad: Multiple bridge calls
  const badExample = () => {
    Animated.timing(value1, { toValue: 100 }).start();
    Animated.timing(value2, { toValue: 200 }).start();
    Animated.timing(value3, { toValue: 300 }).start();
  };

  // Good: Batch operations
  const goodExample = () => {
    Animated.parallel([
      Animated.timing(value1, { toValue: 100 }),
      Animated.timing(value2, { toValue: 200 }),
      Animated.timing(value3, { toValue: 300 }),
    ]).start();
  };
};

Pratiques exemplaires en matière de sécurité

// 1. Secure storage for sensitive data
import { Keychain } from 'react-native-keychain';

const secureStorage = {
  setItem: async (key, value) => {
    await Keychain.setInternetCredentials(key, key, value);
  },

  getItem: async (key) => {
    try {
      const credentials = await Keychain.getInternetCredentials(key);
      return credentials ? credentials.password : null;
    } catch (error) {
      return null;
    }
  },

  removeItem: async (key) => {
    await Keychain.resetInternetCredentials(key);
  },
};

// 2. Certificate pinning
const secureApiCall = async () => {
  const response = await fetch('https://api.example.com/data', {
    method: 'GET',
    headers: {
      'Authorization': `Bearer ${await secureStorage.getItem('token')}`,
    },
  });

  return response.json();
};

// 3. Input validation
const validateInput = (input) => {
  // Sanitize and validate user input
  const sanitized = input.trim().replace(/[<>]/g, '');

  if (sanitized.length < 3) {
    throw new Error('Input too short');
  }

  return sanitized;
};

Dépannage

Questions communes

# Metro bundler issues
npx react-native start --reset-cache

# iOS build issues
cd ios && pod install && cd ..
npx react-native run-ios --clean

# Android build issues
cd android && ./gradlew clean && cd ..
npx react-native run-android --clean

# Clear all caches
npx react-native start --reset-cache
rm -rf node_modules && npm install
cd ios && pod install && cd ..

# Fix permission issues (macOS)
sudo chown -R $(whoami) ~/.npm
sudo chown -R $(whoami) /usr/local/lib/node_modules

Erreurs courantes de débogage

// Network request failed
const handleNetworkError = async () => {
  try {
    const response = await fetch('https://api.example.com/data');
    return await response.json();
  } catch (error) {
    if (error.message === 'Network request failed') {
      // Check network connectivity
      // Verify API endpoint
      // Check CORS settings
    }
    throw error;
  }
};

// Element type is invalid
// Usually caused by incorrect import/export
// Check component imports and exports

// Cannot read property of undefined
const SafeComponent = ({ user }) => {
  // Use optional chaining
  return (
    <View>
      <Text>{user?.name ?? 'Unknown'}</Text>
      <Text>{user?.email ?? 'No email'}</Text>
    </View>
  );
};

// Memory leaks
const ComponentWithCleanup = () => {
  useEffect(() => {
    const subscription = someService.subscribe();

    return () => {
      // Cleanup subscriptions
      subscription.unsubscribe();
    };
  }, []);

  return <View>...</View>;
};

Résumé

React Native est un cadre puissant pour la construction d'applications mobiles multiplateforme qui fournit:

  • Platforme de choc Développement: écrire une fois, exécuter sur iOS et Android
  • Native Performance: Accès direct aux API et composants de plate-forme native
  • Rechargement rapide: Cycle de développement rapide avec rétroaction instantanée
  • Écosystà ̈me de Rich : vaste écosystà ̈me de bibliothà ̈que et soutien communautaire
  • ** Développement familial** : Utilise les concepts React et JavaScript/TypeScript
  • Architecture flexible: Prise en charge de diverses solutions de gestion et de navigation de l'État
  • ** Prêt à la production**: Utilisé par les grandes entreprises comme Facebook, Instagram et Airbnb

React Native excelle à permettre le développement rapide d'applications mobiles tout en maintenant des performances natives et des expériences utilisateur spécifiques à la plateforme. Son écosystème mature, son outillage complet et son solide soutien communautaire en font un excellent choix pour les équipes qui cherchent à construire des applications mobiles de qualité.