30 najczęstszych pytań rekrutacyjnych z React Native: pełny przewodnik 2026

Trzydzieści najczęściej zadawanych pytań rekrutacyjnych z React Native. Szczegółowe odpowiedzi z przykładami kodu, które pomogą zdobyć posadę programisty mobilnego.

Ilustracja pytań rekrutacyjnych React Native z połączonymi komponentami mobilnymi

Rozmowy techniczne z React Native sprawdzają umiejętności w zakresie wieloplatformowego programowania mobilnego, znajomość specyfiki iOS i Androida oraz wzorców wydajnościowych. Niniejszy przewodnik omawia trzydzieści najczęściej zadawanych pytań wraz ze szczegółowymi odpowiedziami i przykładami kodu, dzięki czemu przygotowanie staje się skuteczne i kompletne.

Wskazówka dotycząca przygotowań

Pytania obejmują zarówno podstawy, jak i zaawansowane zagadnienia. Opanowanie architektury React Native oraz zrozumienie różnic względem React dla web jest kluczowe, aby pomyślnie przejść rozmowę.

Podstawy React Native

1. Jaka jest różnica pomiędzy React a React Native?

React stanowi bibliotekę służącą do budowania interfejsów webowych, natomiast React Native umożliwia tworzenie natywnych aplikacji mobilnych dla systemów iOS oraz Android.

Fundamentalna różnica leży w sposobie renderowania: React korzysta z Virtual DOM, który jest tłumaczony na elementy HTML, podczas gdy React Native używa mostu komunikującego się z natywnymi komponentami każdej platformy.

jsx
// React (Web) - uses HTML elements
function WebComponent() {
  return (
    <div className="container">
      <span>Web text</span>
      <button onClick={handleClick}>Click</button>
    </div>
  )
}

// React Native - uses native components
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'

function NativeComponent() {
  return (
    <View style={styles.container}>
      <Text>Native text</Text>
      <TouchableOpacity onPress={handlePress}>
        <Text>Press</Text>
      </TouchableOpacity>
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 16
  }
})

Komponenty React Native są tłumaczone na UIView w iOS oraz android.view w Androidzie, co zapewnia natywną wydajność.

2. Jak działa architektura React Native?

React Native wykorzystuje architekturę trójwarstwową: warstwę JavaScript, warstwę mostu (lub JSI w nowej architekturze) oraz warstwę natywną.

Kod JavaScript wykonuje się w silniku JS (Hermes lub JavaScriptCore). W starej architekturze komunikacja z kodem natywnym odbywa się poprzez serializację JSON, podczas gdy nowa architektura oferuje bezpośrednie wywołania dzięki interfejsowi JSI (JavaScript Interface).

jsx
// Old architecture: asynchronous communication via Bridge
// The Bridge serializes messages as JSON between JS and Native

// New architecture (Fabric + TurboModules)
// JSI enables synchronous direct calls to native modules

// Example TurboModule usage
import { TurboModuleRegistry } from 'react-native'

// Synchronous access to native module
const DeviceInfo = TurboModuleRegistry.get('DeviceInfo')
const deviceName = DeviceInfo.getDeviceName() // Synchronous call

// With Fabric, rendering is smoother
// Components can be created synchronously
// Reducing jank during animations

Nowa architektura znacząco poprawia wydajność, eliminując serializację JSON i umożliwiając synchroniczne wywołania.

3. Czym jest Metro bundler?

Metro to bundler JavaScript stosowany w React Native. Przekształca on kod źródłowy w zoptymalizowany pakiet wykonawczy, gotowy do uruchomienia na urządzeniu mobilnym.

Metro odpowiada za rozwiązywanie modułów, transformację kodu (poprzez Babel) oraz hot reloading podczas pracy deweloperskiej.

metro.config.jsjavascript
const { getDefaultConfig } = require('expo/metro-config')

const config = getDefaultConfig(__dirname)

// Custom configuration
config.resolver.assetExts.push('db') // Add extensions
config.resolver.sourceExts.push('cjs') // CommonJS support

// Transformer configuration
config.transformer.babelTransformerPath = require.resolve(
  'react-native-svg-transformer'
)

// Production optimizations
config.transformer.minifierConfig = {
  keep_classnames: true,
  keep_fnames: true,
  mangle: {
    keep_classnames: true,
    keep_fnames: true
  }
}

module.exports = config

Metro obsługuje także Fast Refresh, dzięki któremu zmiany w kodzie są widoczne natychmiast bez utraty stanu aplikacji.

4. Jak działa StyleSheet.create i jakie daje korzyści?

Funkcja StyleSheet.create optymalizuje style poprzez ich walidację oraz konwersję na referencje numeryczne, co zmniejsza obciążenie mostu.

jsx
// ❌ Inline styles - recreated on every render
function BadExample() {
  return (
    <View style={{ flex: 1, padding: 16, backgroundColor: '#fff' }}>
      <Text style={{ fontSize: 18, fontWeight: 'bold' }}>Title</Text>
    </View>
  )
}

// ✅ StyleSheet.create - optimized and validated
const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 16,
    backgroundColor: '#fff'
  },
  title: {
    fontSize: 18,
    fontWeight: 'bold'
  },
  // Style composition
  row: {
    flexDirection: 'row',
    alignItems: 'center',
    gap: 8
  }
})

function GoodExample() {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Title</Text>
      {/* Style combination */}
      <View style={[styles.row, { marginTop: 10 }]}>
        <Text>Content</Text>
      </View>
    </View>
  )
}

// StyleSheet.absoluteFillObject for absolute positioning
const overlayStyles = StyleSheet.create({
  overlay: {
    ...StyleSheet.absoluteFillObject,
    backgroundColor: 'rgba(0,0,0,0.5)'
  }
})

5. Jaka jest różnica pomiędzy Flexbox webowym a Flexbox w React Native?

React Native korzysta z Flexbox, jednak z innymi domyślnymi wartościami niż wersja webowa, dostosowanymi do pionowych interfejsów mobilnych.

jsx
// Key differences from web
const styles = StyleSheet.create({
  container: {
    // flexDirection: 'column' by default (vs 'row' on web)
    // alignItems: 'stretch' by default
    flex: 1
  },

  // React Native Flexbox
  row: {
    flexDirection: 'row',    // Horizontal
    justifyContent: 'space-between', // Main axis
    alignItems: 'center',    // Cross axis
    flexWrap: 'wrap',        // Line wrapping
    gap: 8                   // Supported since RN 0.71
  },

  // Flex grow/shrink
  flexItem: {
    flex: 1,        // Equivalent to flex: 1 1 0
    flexGrow: 1,    // Grow to fill
    flexShrink: 0,  // Don't shrink
    flexBasis: 100  // Base size
  },

  // Absolute positioning
  absolute: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0
  }
})

// Practical example: card with image and content
function Card() {
  return (
    <View style={cardStyles.container}>
      <Image source={{ uri: imageUrl }} style={cardStyles.image} />
      <View style={cardStyles.content}>
        <Text style={cardStyles.title}>Title</Text>
        <Text style={cardStyles.description}>Description</Text>
      </View>
    </View>
  )
}

const cardStyles = StyleSheet.create({
  container: {
    flexDirection: 'row',
    backgroundColor: '#fff',
    borderRadius: 8,
    overflow: 'hidden'
  },
  image: {
    width: 100,
    height: 100
  },
  content: {
    flex: 1, // Takes remaining space
    padding: 12,
    justifyContent: 'center'
  },
  title: {
    fontSize: 16,
    fontWeight: '600'
  },
  description: {
    fontSize: 14,
    color: '#666'
  }
})

Nawigacja i komponenty

6. Jak zaimplementować nawigację z React Navigation?

React Navigation jest standardowym rozwiązaniem nawigacyjnym w React Native. Oferuje różne typy nawigatorów dopasowanych do mobilnych wzorców interakcji.

jsx
// Installing dependencies
// npm install @react-navigation/native @react-navigation/native-stack
// npm install react-native-screens react-native-safe-area-context

import { NavigationContainer } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'

// TypeScript typing for navigation params
type RootStackParamList = {
  Home: undefined
  Profile: { userId: string }
  Settings: { section?: string }
}

const Stack = createNativeStackNavigator<RootStackParamList>()
const Tab = createBottomTabNavigator()

// Tab navigation
function TabNavigator() {
  return (
    <Tab.Navigator
      screenOptions={({ route }) => ({
        tabBarIcon: ({ focused, color, size }) => {
          // Dynamic icon based on tab
          const iconName = route.name === 'Home' ? 'home' : 'settings'
          return <Icon name={iconName} size={size} color={color} />
        },
        tabBarActiveTintColor: '#007AFF',
        tabBarInactiveTintColor: 'gray'
      })}
    >
      <Tab.Screen name="Home" component={HomeScreen} />
      <Tab.Screen name="Settings" component={SettingsScreen} />
    </Tab.Navigator>
  )
}

// Stack navigation
function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator
        initialRouteName="Home"
        screenOptions={{
          headerStyle: { backgroundColor: '#007AFF' },
          headerTintColor: '#fff',
          animation: 'slide_from_right' // Native animation
        }}
      >
        <Stack.Screen
          name="Home"
          component={TabNavigator}
          options={{ headerShown: false }}
        />
        <Stack.Screen
          name="Profile"
          component={ProfileScreen}
          options={({ route }) => ({
            title: `Profile ${route.params.userId}`
          })}
        />
      </Stack.Navigator>
    </NavigationContainer>
  )
}

7. Jak obsłużyć wydajne listy z FlatList?

Komponent FlatList jest zoptymalizowany pod kątem długich list dzięki automatycznej wirtualizacji, renderując wyłącznie widoczne elementy.

jsx
import { FlatList, RefreshControl } from 'react-native'

function ProductList() {
  const [products, setProducts] = useState([])
  const [refreshing, setRefreshing] = useState(false)
  const [loading, setLoading] = useState(false)

  // Initial loading
  const fetchProducts = async (page = 1) => {
    const response = await api.getProducts(page)
    return response.data
  }

  // Pull-to-refresh
  const onRefresh = useCallback(async () => {
    setRefreshing(true)
    const data = await fetchProducts(1)
    setProducts(data)
    setRefreshing(false)
  }, [])

  // Infinite pagination
  const loadMore = useCallback(async () => {
    if (loading) return
    setLoading(true)
    const nextPage = Math.ceil(products.length / 20) + 1
    const data = await fetchProducts(nextPage)
    setProducts(prev => [...prev, ...data])
    setLoading(false)
  }, [products.length, loading])

  // Item rendering
  const renderItem = useCallback(({ item }) => (
    <ProductCard product={item} />
  ), [])

  // Key extraction
  const keyExtractor = useCallback((item) => item.id.toString(), [])

  // Item separator
  const ItemSeparator = useCallback(() => (
    <View style={{ height: 12 }} />
  ), [])

  return (
    <FlatList
      data={products}
      renderItem={renderItem}
      keyExtractor={keyExtractor}
      ItemSeparatorComponent={ItemSeparator}
      // Performance optimizations
      removeClippedSubviews={true}
      maxToRenderPerBatch={10}
      updateCellsBatchingPeriod={50}
      windowSize={5}
      // Pull-to-refresh
      refreshControl={
        <RefreshControl
          refreshing={refreshing}
          onRefresh={onRefresh}
          tintColor="#007AFF"
        />
      }
      // Infinite pagination
      onEndReached={loadMore}
      onEndReachedThreshold={0.5}
      ListFooterComponent={loading ? <ActivityIndicator /> : null}
      // Empty list
      ListEmptyComponent={<EmptyState message="No products" />}
    />
  )
}
Optymalizacja FlatList

Warto zawsze memoizować renderItem przy użyciu useCallback oraz wydzielać ciężkie komponenty. Należy unikać funkcji inline w renderItem, ponieważ powodują niepotrzebne ponowne renderowania.

8. Jaka jest różnica pomiędzy TouchableOpacity, Pressable i TouchableHighlight?

Te komponenty obsługują interakcje dotykowe, jednak różnią się sposobem prezentacji wizualnego sprzężenia zwrotnego.

jsx
import {
  TouchableOpacity,
  TouchableHighlight,
  Pressable,
  StyleSheet
} from 'react-native'

function InteractionExamples() {
  return (
    <View style={styles.container}>
      {/* TouchableOpacity: reduces opacity on touch */}
      <TouchableOpacity
        activeOpacity={0.7}
        onPress={() => console.log('Pressed')}
        style={styles.button}
      >
        <Text>TouchableOpacity</Text>
      </TouchableOpacity>

      {/* TouchableHighlight: adds background color */}
      <TouchableHighlight
        underlayColor="#ddd"
        onPress={() => console.log('Pressed')}
        style={styles.button}
      >
        <Text>TouchableHighlight</Text>
      </TouchableHighlight>

      {/* Pressable: modern API with more control */}
      <Pressable
        onPress={() => console.log('Pressed')}
        onLongPress={() => console.log('Long press')}
        delayLongPress={500}
        style={({ pressed }) => [
          styles.button,
          pressed && styles.buttonPressed
        ]}
      >
        {({ pressed }) => (
          <Text style={pressed && styles.textPressed}>
            {pressed ? 'Pressed!' : 'Pressable'}
          </Text>
        )}
      </Pressable>

      {/* Pressable with hitSlop to enlarge touch area */}
      <Pressable
        hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
        onPress={() => console.log('Pressed')}
        style={styles.smallButton}
      >
        <Text>Small button</Text>
      </Pressable>
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    gap: 16,
    padding: 20
  },
  button: {
    backgroundColor: '#007AFF',
    padding: 16,
    borderRadius: 8,
    alignItems: 'center'
  },
  buttonPressed: {
    backgroundColor: '#0056b3',
    transform: [{ scale: 0.98 }]
  },
  textPressed: {
    color: '#fff'
  },
  smallButton: {
    padding: 8,
    backgroundColor: '#eee'
  }
})

W nowych projektach zaleca się stosowanie Pressable, ponieważ oferuje większą kontrolę i bardziej spójne API.

9. Jak tworzyć płynne animacje?

React Native udostępnia kilka API do animacji: wbudowane Animated oraz wydajniejsze Reanimated.

jsx
import { Animated, Easing } from 'react-native'
import Reanimated, {
  useSharedValue,
  useAnimatedStyle,
  withSpring,
  withTiming
} from 'react-native-reanimated'

// Animation with Animated (native API)
function FadeInView({ children }) {
  const fadeAnim = useRef(new Animated.Value(0)).current

  useEffect(() => {
    Animated.timing(fadeAnim, {
      toValue: 1,
      duration: 500,
      easing: Easing.ease,
      useNativeDriver: true // Performant on UI thread
    }).start()
  }, [])

  return (
    <Animated.View style={{ opacity: fadeAnim }}>
      {children}
    </Animated.View>
  )
}

// Animation with Reanimated (recommended for complex animations)
function BouncyButton() {
  const scale = useSharedValue(1)

  const animatedStyle = useAnimatedStyle(() => ({
    transform: [{ scale: scale.value }]
  }))

  const handlePressIn = () => {
    scale.value = withSpring(0.95, {
      damping: 10,
      stiffness: 400
    })
  }

  const handlePressOut = () => {
    scale.value = withSpring(1, {
      damping: 10,
      stiffness: 400
    })
  }

  return (
    <Pressable onPressIn={handlePressIn} onPressOut={handlePressOut}>
      <Reanimated.View style={[styles.button, animatedStyle]}>
        <Text style={styles.buttonText}>Press</Text>
      </Reanimated.View>
    </Pressable>
  )
}

// List animation with LayoutAnimation
import { LayoutAnimation, UIManager, Platform } from 'react-native'

// Enable on Android
if (Platform.OS === 'android') {
  UIManager.setLayoutAnimationEnabledExperimental?.(true)
}

function AnimatedList() {
  const [items, setItems] = useState([])

  const addItem = () => {
    // Configure animation before state change
    LayoutAnimation.configureNext(LayoutAnimation.Presets.spring)
    setItems(prev => [...prev, { id: Date.now() }])
  }

  const removeItem = (id) => {
    LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
    setItems(prev => prev.filter(item => item.id !== id))
  }

  return (
    <View>
      <Button title="Add" onPress={addItem} />
      {items.map(item => (
        <TouchableOpacity key={item.id} onPress={() => removeItem(item.id)}>
          <View style={styles.item}>
            <Text>Item {item.id}</Text>
          </View>
        </TouchableOpacity>
      ))}
    </View>
  )
}

Gotowy na rozmowy o React Native?

Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.

Stan i zarządzanie danymi

10. Jak zarządzać stanem globalnym w React Native?

Dostępne są te same rozwiązania co w React dla web: Context API, Redux, Zustand lub MobX.

jsx
// Lightweight solution with Zustand
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'
import AsyncStorage from '@react-native-async-storage/async-storage'

// Store with persistence
const useAuthStore = create(
  persist(
    (set, get) => ({
      user: null,
      token: null,
      isAuthenticated: false,

      login: async (email, password) => {
        const response = await api.login(email, password)
        set({
          user: response.user,
          token: response.token,
          isAuthenticated: true
        })
      },

      logout: () => {
        set({ user: null, token: null, isAuthenticated: false })
      },

      updateProfile: (updates) => {
        set(state => ({
          user: { ...state.user, ...updates }
        }))
      }
    }),
    {
      name: 'auth-storage',
      storage: createJSONStorage(() => AsyncStorage)
    }
  )
)

// Usage in a component
function ProfileScreen() {
  const { user, logout, updateProfile } = useAuthStore()

  if (!user) return <LoginPrompt />

  return (
    <View style={styles.container}>
      <Text style={styles.name}>{user.name}</Text>
      <Text style={styles.email}>{user.email}</Text>

      <TouchableOpacity onPress={logout} style={styles.logoutButton}>
        <Text>Logout</Text>
      </TouchableOpacity>
    </View>
  )
}

// Cart store
const useCartStore = create((set, get) => ({
  items: [],

  addItem: (product) => set(state => {
    const existing = state.items.find(i => i.id === product.id)
    if (existing) {
      return {
        items: state.items.map(i =>
          i.id === product.id
            ? { ...i, quantity: i.quantity + 1 }
            : i
        )
      }
    }
    return { items: [...state.items, { ...product, quantity: 1 }] }
  }),

  removeItem: (id) => set(state => ({
    items: state.items.filter(i => i.id !== id)
  })),

  getTotal: () => {
    return get().items.reduce(
      (sum, item) => sum + item.price * item.quantity,
      0
    )
  },

  clearCart: () => set({ items: [] })
}))

11. Jak wykonywać wywołania API z zarządzaniem cache?

React Query (TanStack Query) stanowi rekomendowane rozwiązanie do obsługi danych pochodzących z serwera.

jsx
import { QueryClient, QueryClientProvider, useQuery, useMutation } from '@tanstack/react-query'

// Client configuration
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 5 * 60 * 1000, // 5 minutes
      cacheTime: 30 * 60 * 1000, // 30 minutes
      retry: 2,
      refetchOnWindowFocus: false // Mobile doesn't have "window focus"
    }
  }
})

// Provider in App.tsx
function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <NavigationContainer>
        <AppNavigator />
      </NavigationContainer>
    </QueryClientProvider>
  )
}

// Custom hook for products
function useProducts(categoryId) {
  return useQuery({
    queryKey: ['products', categoryId],
    queryFn: async () => {
      const response = await fetch(`/api/products?category=${categoryId}`)
      if (!response.ok) throw new Error('Network error')
      return response.json()
    },
    enabled: !!categoryId // Don't execute if no categoryId
  })
}

// Mutation with cache invalidation
function useAddToCart() {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: async (product) => {
      const response = await fetch('/api/cart', {
        method: 'POST',
        body: JSON.stringify(product)
      })
      return response.json()
    },
    onSuccess: () => {
      // Invalidate cart cache to force refetch
      queryClient.invalidateQueries({ queryKey: ['cart'] })
    },
    onError: (error) => {
      Alert.alert('Error', error.message)
    }
  })
}

// Usage in a component
function ProductListScreen({ categoryId }) {
  const { data: products, isLoading, error, refetch } = useProducts(categoryId)
  const addToCart = useAddToCart()

  if (isLoading) return <LoadingSpinner />
  if (error) return <ErrorView error={error} onRetry={refetch} />

  return (
    <FlatList
      data={products}
      renderItem={({ item }) => (
        <ProductCard
          product={item}
          onAddToCart={() => addToCart.mutate(item)}
          isAddingToCart={addToCart.isPending}
        />
      )}
      refreshing={isLoading}
      onRefresh={refetch}
    />
  )
}

12. Jak przechowywać dane lokalnie?

Dostępnych jest kilka opcji w zależności od typu danych: AsyncStorage dla prostych danych, MMKV dla wydajności i SQLite dla danych strukturalnych.

jsx
// AsyncStorage - simple but slow for large volumes
import AsyncStorage from '@react-native-async-storage/async-storage'

const storage = {
  async get(key) {
    const value = await AsyncStorage.getItem(key)
    return value ? JSON.parse(value) : null
  },

  async set(key, value) {
    await AsyncStorage.setItem(key, JSON.stringify(value))
  },

  async remove(key) {
    await AsyncStorage.removeItem(key)
  },

  async clear() {
    await AsyncStorage.clear()
  }
}

// MMKV - very performant (recommended)
import { MMKV } from 'react-native-mmkv'

const mmkv = new MMKV()

const fastStorage = {
  get(key) {
    const value = mmkv.getString(key)
    return value ? JSON.parse(value) : null
  },

  set(key, value) {
    mmkv.set(key, JSON.stringify(value))
  },

  remove(key) {
    mmkv.delete(key)
  },

  // Primitive type support
  getNumber(key) {
    return mmkv.getNumber(key)
  },

  setNumber(key, value) {
    mmkv.set(key, value)
  },

  getBoolean(key) {
    return mmkv.getBoolean(key)
  }
}

// Hook to use MMKV with React
function useMMKVStorage(key, defaultValue) {
  const [value, setValue] = useState(() => {
    const stored = fastStorage.get(key)
    return stored ?? defaultValue
  })

  const setStoredValue = useCallback((newValue) => {
    setValue(newValue)
    fastStorage.set(key, newValue)
  }, [key])

  return [value, setStoredValue]
}

// Usage
function SettingsScreen() {
  const [theme, setTheme] = useMMKVStorage('theme', 'light')
  const [notifications, setNotifications] = useMMKVStorage('notifications', true)

  return (
    <View>
      <Switch
        value={theme === 'dark'}
        onValueChange={(v) => setTheme(v ? 'dark' : 'light')}
      />
      <Switch
        value={notifications}
        onValueChange={setNotifications}
      />
    </View>
  )
}
Dane wrażliwe

Dla tokenów oraz danych wrażliwych warto stosować expo-secure-store lub react-native-keychain, które szyfrują dane poprzez Keychain (iOS) oraz Keystore (Android).

Wydajność i optymalizacja

13. Jak optymalizować wydajność aplikacji React Native?

Optymalizacja obejmuje kilka aspektów: renderowanie, pamięć oraz interakcje.

1. Avoid unnecessary re-renders with React.memojsx
const ProductCard = React.memo(function ProductCard({ product, onPress }) {
  return (
    <TouchableOpacity onPress={() => onPress(product.id)}>
      <View style={styles.card}>
        <Image source={{ uri: product.image }} style={styles.image} />
        <Text style={styles.title}>{product.name}</Text>
        <Text style={styles.price}>{product.price}$</Text>
      </View>
    </TouchableOpacity>
  )
}, (prevProps, nextProps) => {
  // Custom comparison
  return prevProps.product.id === nextProps.product.id
})

// 2. Memoize callbacks
function ProductList({ products }) {
  // ❌ New function on every render
  // onPress={(id) => handlePress(id)}

  // ✅ Stable function
  const handlePress = useCallback((id) => {
    navigation.navigate('Product', { id })
  }, [navigation])

  return (
    <FlatList
      data={products}
      renderItem={({ item }) => (
        <ProductCard product={item} onPress={handlePress} />
      )}
    />
  )
}

// 3. Optimize images
import FastImage from 'react-native-fast-image'

function OptimizedImage({ uri }) {
  return (
    <FastImage
      source={{
        uri,
        priority: FastImage.priority.normal,
        cache: FastImage.cacheControl.immutable
      }}
      style={styles.image}
      resizeMode={FastImage.resizeMode.cover}
    />
  )
}

// 4. Use InteractionManager for heavy tasks
import { InteractionManager } from 'react-native'

function HeavyScreen() {
  const [data, setData] = useState(null)

  useEffect(() => {
    // Wait for animations to complete
    const task = InteractionManager.runAfterInteractions(() => {
      const result = performHeavyComputation()
      setData(result)
    })

    return () => task.cancel()
  }, [])

  return data ? <DataView data={data} /> : <LoadingView />
}

// 5. Lazy loading screens
const HeavyScreen = React.lazy(() => import('./HeavyScreen'))

function Navigator() {
  return (
    <Stack.Navigator>
      <Stack.Screen
        name="Heavy"
        component={HeavyScreen}
        options={{ lazy: true }}
      />
    </Stack.Navigator>
  )
}

14. Jak debugować problemy z wydajnością?

React Native dostarcza kilka narzędzi pomocnych w identyfikowaniu wąskich gardeł.

1. Flipper - main debugging tooljsx
// Configuration in android/app/build.gradle and ios/Podfile
// See: https://fbflipper.com/

// 2. Console.time to measure operations
function fetchData() {
  console.time('fetchData')
  const data = await api.getData()
  console.timeEnd('fetchData') // fetchData: 234ms
  return data
}

// 3. Performance monitor (shake device → Show Perf Monitor)
// Shows JS and UI FPS

// 4. Hermes profiler for CPU
// Enable in metro.config.js
module.exports = {
  transformer: {
    hermesParser: true
  }
}

// 5. Identify re-renders with why-did-you-render
// Installation: npm install @welldone-software/why-did-you-render
import React from 'react'

if (__DEV__) {
  const whyDidYouRender = require('@welldone-software/why-did-you-render')
  whyDidYouRender(React, {
    trackAllPureComponents: true
  })
}

// Mark a component to monitor
ProductCard.whyDidYouRender = true

// 6. Measure mount time
function useComponentTiming(componentName) {
  const mountTime = useRef(Date.now())

  useEffect(() => {
    const duration = Date.now() - mountTime.current
    console.log(`${componentName} mounted in ${duration}ms`)

    return () => {
      console.log(`${componentName} unmounted`)
    }
  }, [componentName])
}

// Usage
function MyComponent() {
  useComponentTiming('MyComponent')
  // ...
}

15. Jak obsługiwać tryb offline?

Obsługa trybu offline wymaga strategii cache oraz synchronizacji.

jsx
import NetInfo from '@react-native-community/netinfo'

// Hook to monitor connectivity
function useNetworkStatus() {
  const [isConnected, setIsConnected] = useState(true)
  const [connectionType, setConnectionType] = useState(null)

  useEffect(() => {
    const unsubscribe = NetInfo.addEventListener(state => {
      setIsConnected(state.isConnected)
      setConnectionType(state.type)
    })

    return () => unsubscribe()
  }, [])

  return { isConnected, connectionType }
}

// Service with offline queue
class OfflineQueue {
  constructor() {
    this.queue = []
    this.isProcessing = false
  }

  async add(action) {
    this.queue.push({
      id: Date.now(),
      action,
      timestamp: new Date().toISOString()
    })
    await this.persist()
  }

  async persist() {
    await AsyncStorage.setItem('offline_queue', JSON.stringify(this.queue))
  }

  async load() {
    const data = await AsyncStorage.getItem('offline_queue')
    this.queue = data ? JSON.parse(data) : []
  }

  async process() {
    if (this.isProcessing || this.queue.length === 0) return

    this.isProcessing = true
    const { isConnected } = await NetInfo.fetch()

    if (!isConnected) {
      this.isProcessing = false
      return
    }

    while (this.queue.length > 0) {
      const item = this.queue[0]
      try {
        await this.executeAction(item.action)
        this.queue.shift()
        await this.persist()
      } catch (error) {
        console.error('Failed to process action:', error)
        break
      }
    }

    this.isProcessing = false
  }

  async executeAction(action) {
    switch (action.type) {
      case 'CREATE_ORDER':
        return api.createOrder(action.payload)
      case 'UPDATE_PROFILE':
        return api.updateProfile(action.payload)
      default:
        throw new Error(`Unknown action: ${action.type}`)
    }
  }
}

const offlineQueue = new OfflineQueue()

// Network status banner component
function NetworkBanner() {
  const { isConnected } = useNetworkStatus()

  if (isConnected) return null

  return (
    <View style={styles.banner}>
      <Text style={styles.bannerText}>
        Offline mode - Changes will be synchronized
      </Text>
    </View>
  )
}

Specyfika mobilna

16. Jak obsługiwać uprawnienia w iOS i Android?

Uprawnienia są obsługiwane inaczej na każdej platformie. Biblioteki takie jak react-native-permissions ujednolicają API.

jsx
import { Platform, Alert, Linking } from 'react-native'
import {
  check,
  request,
  PERMISSIONS,
  RESULTS,
  openSettings
} from 'react-native-permissions'

// Permission configuration per platform
const PERMISSION_TYPES = {
  camera: Platform.select({
    ios: PERMISSIONS.IOS.CAMERA,
    android: PERMISSIONS.ANDROID.CAMERA
  }),
  photos: Platform.select({
    ios: PERMISSIONS.IOS.PHOTO_LIBRARY,
    android: PERMISSIONS.ANDROID.READ_MEDIA_IMAGES
  }),
  location: Platform.select({
    ios: PERMISSIONS.IOS.LOCATION_WHEN_IN_USE,
    android: PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION
  })
}

// Hook to manage permissions
function usePermission(type) {
  const [status, setStatus] = useState(RESULTS.UNAVAILABLE)

  const permission = PERMISSION_TYPES[type]

  useEffect(() => {
    check(permission).then(setStatus)
  }, [permission])

  const requestPermission = useCallback(async () => {
    const result = await request(permission)
    setStatus(result)

    if (result === RESULTS.BLOCKED) {
      Alert.alert(
        'Permission required',
        `This feature requires ${type} access. Do you want to open settings?`,
        [
          { text: 'No', style: 'cancel' },
          { text: 'Open', onPress: openSettings }
        ]
      )
    }

    return result
  }, [permission, type])

  return {
    status,
    isGranted: status === RESULTS.GRANTED,
    isDenied: status === RESULTS.DENIED,
    isBlocked: status === RESULTS.BLOCKED,
    requestPermission
  }
}

// Usage
function CameraButton() {
  const { isGranted, requestPermission } = usePermission('camera')

  const handlePress = async () => {
    if (!isGranted) {
      const result = await requestPermission()
      if (result !== RESULTS.GRANTED) return
    }
    // Open camera
    navigation.navigate('Camera')
  }

  return (
    <TouchableOpacity onPress={handlePress}>
      <Text>Take a photo</Text>
    </TouchableOpacity>
  )
}

17. Jak zaimplementować powiadomienia push?

Powiadomienia push wymagają konfiguracji natywnej oraz backendu odpowiedzialnego za wysyłkę.

jsx
import messaging from '@react-native-firebase/messaging'
import notifee, { AndroidImportance } from '@notifee/react-native'

// Notification service
class NotificationService {
  async initialize() {
    // Request permission (iOS)
    const authStatus = await messaging().requestPermission()
    const enabled = authStatus === messaging.AuthorizationStatus.AUTHORIZED

    if (enabled) {
      // Get FCM token
      const token = await messaging().getToken()
      await this.registerToken(token)

      // Listen for token changes
      messaging().onTokenRefresh(this.registerToken)

      // Create Android channel
      await notifee.createChannel({
        id: 'default',
        name: 'Notifications',
        importance: AndroidImportance.HIGH
      })
    }

    return enabled
  }

  async registerToken(token) {
    // Send token to backend
    await api.registerPushToken(token)
  }

  // Setup handlers
  setupHandlers() {
    // Notification received in foreground
    messaging().onMessage(async (remoteMessage) => {
      await this.displayNotification(remoteMessage)
    })

    // Notification tapped (app in background)
    messaging().onNotificationOpenedApp((remoteMessage) => {
      this.handleNotificationPress(remoteMessage)
    })

    // App opened from notification (app closed)
    messaging()
      .getInitialNotification()
      .then((remoteMessage) => {
        if (remoteMessage) {
          this.handleNotificationPress(remoteMessage)
        }
      })
  }

  async displayNotification(remoteMessage) {
    const { title, body } = remoteMessage.notification

    await notifee.displayNotification({
      title,
      body,
      android: {
        channelId: 'default',
        pressAction: { id: 'default' }
      },
      data: remoteMessage.data
    })
  }

  handleNotificationPress(remoteMessage) {
    const { type, id } = remoteMessage.data

    switch (type) {
      case 'order':
        navigation.navigate('OrderDetail', { orderId: id })
        break
      case 'message':
        navigation.navigate('Chat', { conversationId: id })
        break
    }
  }
}

// Usage in App.tsx
function App() {
  useEffect(() => {
    const notificationService = new NotificationService()
    notificationService.initialize()
    notificationService.setupHandlers()
  }, [])

  return <AppNavigator />
}

Deep links umożliwiają otwarcie aplikacji bezpośrednio na konkretnym ekranie z poziomu zewnętrznego URL.

jsx
import { Linking } from 'react-native'
import { NavigationContainer } from '@react-navigation/native'

// Deep link configuration
const linking = {
  prefixes: ['myapp://', 'https://myapp.com'],

  config: {
    screens: {
      Home: 'home',
      Product: {
        path: 'product/:id',
        parse: {
          id: (id) => parseInt(id, 10)
        }
      },
      Profile: 'profile/:userId?',
      Settings: {
        path: 'settings',
        screens: {
          Notifications: 'notifications',
          Privacy: 'privacy'
        }
      }
    }
  },

  // Custom function to get initial URL
  async getInitialURL() {
    // Check if app was opened via deep link
    const url = await Linking.getInitialURL()
    if (url) return url

    // Check notifications
    const message = await messaging().getInitialNotification()
    if (message?.data?.link) return message.data.link

    return null
  },

  // Subscribe to incoming links
  subscribe(listener) {
    // Standard deep links
    const linkingSubscription = Linking.addEventListener('url', ({ url }) => {
      listener(url)
    })

    // Links from notifications
    const unsubscribeNotification = messaging().onNotificationOpenedApp(
      (message) => {
        const link = message.data?.link
        if (link) listener(link)
      }
    )

    return () => {
      linkingSubscription.remove()
      unsubscribeNotification()
    }
  }
}

// Usage
function App() {
  return (
    <NavigationContainer linking={linking} fallback={<LoadingScreen />}>
      <Stack.Navigator>
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="Product" component={ProductScreen} />
        <Stack.Screen name="Profile" component={ProfileScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  )
}

// Testing deep links
// myapp://product/123
// https://myapp.com/profile/user456

Gotowy na rozmowy o React Native?

Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.

Kod natywny i moduły

19. Kiedy i jak pisać kod natywny?

Kod natywny jest niezbędny, by uzyskać dostęp do funkcjonalności niedostępnych w JavaScript lub by zoptymalizować krytyczną wydajność.

jsx
// iOS - Native module in Swift
// ios/MyModule.swift
import Foundation

@objc(MyModule)
class MyModule: NSObject {

  @objc
  func getDeviceInfo(_ resolve: @escaping RCTPromiseResolveBlock,
                     rejecter reject: @escaping RCTPromiseRejectBlock) {
    let info: [String: Any] = [
      "model": UIDevice.current.model,
      "systemVersion": UIDevice.current.systemVersion,
      "name": UIDevice.current.name
    ]
    resolve(info)
  }

  @objc
  static func requiresMainQueueSetup() -> Bool {
    return false
  }
}

// ios/MyModule.m (Bridge)
#import <React/RCTBridgeModule.h>

@interface RCT_EXTERN_MODULE(MyModule, NSObject)

RCT_EXTERN_METHOD(getDeviceInfo:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)

@end

// Android - Native module in Kotlin
// android/app/src/main/java/com/myapp/MyModule.kt
package com.myapp

import com.facebook.react.bridge.*

class MyModule(reactContext: ReactApplicationContext) :
    ReactContextBaseJavaModule(reactContext) {

    override fun getName() = "MyModule"

    @ReactMethod
    fun getDeviceInfo(promise: Promise) {
        val info = Arguments.createMap().apply {
            putString("model", android.os.Build.MODEL)
            putString("systemVersion", android.os.Build.VERSION.RELEASE)
            putString("manufacturer", android.os.Build.MANUFACTURER)
        }
        promise.resolve(info)
    }
}

// JavaScript - Using native module
import { NativeModules } from 'react-native'

const { MyModule } = NativeModules

async function getDeviceInfo() {
  try {
    const info = await MyModule.getDeviceInfo()
    console.log('Device info:', info)
    return info
  } catch (error) {
    console.error('Error getting device info:', error)
    throw error
  }
}

20. Czym jest Expo i kiedy go stosować?

Expo to framework upraszczający tworzenie aplikacji w React Native poprzez automatyczne zarządzanie konfiguracją natywną.

jsx
// Expo benefits
// - No need for Android Studio or Xcode to start
// - OTA (over-the-air) updates without app stores
// - Rich SDK with preconfigured modules
// - EAS Build for cloud builds

// Creating an Expo project
// npx create-expo-app@latest MyApp

// Using Expo modules
import * as ImagePicker from 'expo-image-picker'
import * as Location from 'expo-location'
import * as Notifications from 'expo-notifications'

async function pickImage() {
  // Request permission
  const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync()

  if (status !== 'granted') {
    alert('Permission denied')
    return
  }

  // Open image picker
  const result = await ImagePicker.launchImageLibraryAsync({
    mediaTypes: ImagePicker.MediaTypeOptions.Images,
    allowsEditing: true,
    aspect: [4, 3],
    quality: 0.8
  })

  if (!result.canceled) {
    return result.assets[0].uri
  }
}

// Build configuration
// app.json
{
  "expo": {
    "name": "MyApp",
    "slug": "myapp",
    "version": "1.0.0",
    "orientation": "portrait",
    "icon": "./assets/icon.png",
    "splash": {
      "image": "./assets/splash.png",
      "resizeMode": "contain",
      "backgroundColor": "#ffffff"
    },
    "ios": {
      "supportsTablet": true,
      "bundleIdentifier": "com.company.myapp"
    },
    "android": {
      "adaptiveIcon": {
        "foregroundImage": "./assets/adaptive-icon.png",
        "backgroundColor": "#ffffff"
      },
      "package": "com.company.myapp"
    },
    "plugins": [
      "expo-router",
      [
        "expo-camera",
        {
          "cameraPermission": "Allow camera access"
        }
      ]
    ]
  }
}

// When NOT to use Expo
// - Need for complex custom native modules
// - Integration with specific native SDKs
// - Total control over native configuration
// - Very lightweight application (Expo adds weight)

21. Jak działa Hot Reloading?

Hot Reloading (Fast Refresh) umożliwia natychmiastowe wprowadzanie zmian bez utraty stanu aplikacji.

jsx
// Fast Refresh preserves hooks state
function Counter() {
  const [count, setCount] = useState(0)

  // Modify this text and save
  // The count state will be preserved
  return (
    <View>
      <Text>Counter: {count}</Text>
      <Button title="+1" onPress={() => setCount(c => c + 1)} />
    </View>
  )
}

// ⚠️ Cases where Fast Refresh does full reload
// 1. Syntax error
// 2. Modifying a class component
// 3. File with mixed non-component exports

// ❌ This file will do full reload because of mixed exports
export const API_URL = 'https://api.example.com'
export function MyComponent() { /* ... */ }

// ✅ Separate into distinct files
// constants.js
export const API_URL = 'https://api.example.com'

// MyComponent.js
export function MyComponent() { /* ... */ }

// Force remount if needed
// Add this comment at top of file:
// @refresh reset

// Metro configuration for Fast Refresh
// metro.config.js
module.exports = {
  transformer: {
    experimentalImportSupport: false,
    inlineRequires: true
  }
}

Testowanie i jakość

22. Jak testować aplikację React Native?

Testowanie aplikacji React Native odbywa się z wykorzystaniem Jest oraz wyspecjalizowanych bibliotek do renderowania i interakcji.

jsx
// Jest configuration
// jest.config.js
module.exports = {
  preset: 'react-native',
  setupFilesAfterEnv: ['@testing-library/jest-native/extend-expect'],
  transformIgnorePatterns: [
    'node_modules/(?!(react-native|@react-native|@react-navigation)/)'
  ],
  moduleNameMapper: {
    '\\.(jpg|jpeg|png|gif|webp|svg)$': '<rootDir>/__mocks__/fileMock.js'
  }
}

// Component test with React Native Testing Library
import { render, screen, fireEvent, waitFor } from '@testing-library/react-native'
import { ProductCard } from './ProductCard'

describe('ProductCard', () => {
  const mockProduct = {
    id: '1',
    name: 'iPhone 15',
    price: 999,
    image: 'https://example.com/iphone.jpg'
  }

  it('renders product information', () => {
    render(<ProductCard product={mockProduct} />)

    expect(screen.getByText('iPhone 15')).toBeOnTheScreen()
    expect(screen.getByText('$999')).toBeOnTheScreen()
  })

  it('calls onPress when tapped', () => {
    const onPress = jest.fn()
    render(<ProductCard product={mockProduct} onPress={onPress} />)

    fireEvent.press(screen.getByTestId('product-card'))

    expect(onPress).toHaveBeenCalledWith('1')
  })

  it('shows loading state when adding to cart', async () => {
    const onAddToCart = jest.fn(() => new Promise(r => setTimeout(r, 100)))
    render(<ProductCard product={mockProduct} onAddToCart={onAddToCart} />)

    fireEvent.press(screen.getByText('Add to cart'))

    expect(screen.getByTestId('loading-indicator')).toBeOnTheScreen()

    await waitFor(() => {
      expect(screen.queryByTestId('loading-indicator')).not.toBeOnTheScreen()
    })
  })
})

// Navigation test
import { NavigationContainer } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'

const Stack = createNativeStackNavigator()

function renderWithNavigation(component, { initialRouteName = 'Test' } = {}) {
  return render(
    <NavigationContainer>
      <Stack.Navigator initialRouteName={initialRouteName}>
        <Stack.Screen name="Test" component={component} />
        <Stack.Screen name="Detail" component={DetailScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  )
}

// Custom hook test
import { renderHook, act } from '@testing-library/react-native'
import { useCounter } from './useCounter'

test('increments counter', () => {
  const { result } = renderHook(() => useCounter(0))

  act(() => {
    result.current.increment()
  })

  expect(result.current.count).toBe(1)
})

23. Jak zaimplementować testy E2E z Detox?

Detox umożliwia testowanie aplikacji na rzeczywistych symulatorach oraz emulatorach.

jsx
// Installation
// npm install detox --save-dev
// detox init -r jest

// .detoxrc.js
module.exports = {
  testRunner: {
    args: {
      $0: 'jest',
      config: 'e2e/jest.config.js'
    },
    jest: {
      setupTimeout: 120000
    }
  },
  apps: {
    'ios.debug': {
      type: 'ios.app',
      binaryPath: 'ios/build/MyApp.app',
      build: 'xcodebuild -workspace ios/MyApp.xcworkspace -scheme MyApp -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build'
    },
    'android.debug': {
      type: 'android.apk',
      binaryPath: 'android/app/build/outputs/apk/debug/app-debug.apk',
      build: 'cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug'
    }
  },
  devices: {
    simulator: {
      type: 'ios.simulator',
      device: { type: 'iPhone 15' }
    },
    emulator: {
      type: 'android.emulator',
      device: { avdName: 'Pixel_5_API_34' }
    }
  }
}

// e2e/login.test.js
describe('Login Flow', () => {
  beforeAll(async () => {
    await device.launchApp()
  })

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

  it('should login successfully with valid credentials', async () => {
    // Fill the form
    await element(by.id('email-input')).typeText('test@example.com')
    await element(by.id('password-input')).typeText('password123')

    // Submit
    await element(by.id('login-button')).tap()

    // Verify navigation to dashboard
    await waitFor(element(by.text('Welcome')))
      .toBeVisible()
      .withTimeout(5000)
  })

  it('should show error with invalid credentials', async () => {
    await element(by.id('email-input')).typeText('wrong@example.com')
    await element(by.id('password-input')).typeText('wrongpassword')
    await element(by.id('login-button')).tap()

    await expect(element(by.text('Invalid credentials'))).toBeVisible()
  })

  it('should navigate to forgot password', async () => {
    await element(by.id('forgot-password-link')).tap()

    await expect(element(by.text('Reset Password'))).toBeVisible()
  })
})

// Detox commands
// detox build --configuration ios.debug
// detox test --configuration ios.debug

Wdrożenie i produkcja

24. Jak zarządzać środowiskami (dev, staging, prod)?

Zarządzanie środowiskami wymaga rozdzielenia zmiennych konfiguracyjnych.

jsx
// Option 1: react-native-config
// .env.development
API_URL=https://dev-api.myapp.com
ANALYTICS_KEY=dev_key

// .env.staging
API_URL=https://staging-api.myapp.com
ANALYTICS_KEY=staging_key

// .env.production
API_URL=https://api.myapp.com
ANALYTICS_KEY=prod_key

// Usage
import Config from 'react-native-config'

const api = {
  baseUrl: Config.API_URL,
  analyticsKey: Config.ANALYTICS_KEY
}

// Option 2: JavaScript configuration file
// config/index.js
const ENV = {
  development: {
    apiUrl: 'https://dev-api.myapp.com',
    analyticsEnabled: false,
    logLevel: 'debug'
  },
  staging: {
    apiUrl: 'https://staging-api.myapp.com',
    analyticsEnabled: true,
    logLevel: 'info'
  },
  production: {
    apiUrl: 'https://api.myapp.com',
    analyticsEnabled: true,
    logLevel: 'error'
  }
}

const getEnv = () => {
  if (__DEV__) return 'development'
  // Logic to determine staging vs prod
  return 'production'
}

export const config = ENV[getEnv()]

// Option 3: Expo with app.config.js
// app.config.js
export default ({ config }) => {
  const env = process.env.APP_ENV || 'development'

  const envConfig = {
    development: {
      apiUrl: 'https://dev-api.myapp.com',
      bundleId: 'com.myapp.dev'
    },
    production: {
      apiUrl: 'https://api.myapp.com',
      bundleId: 'com.myapp'
    }
  }

  return {
    ...config,
    extra: {
      ...envConfig[env],
      env
    },
    ios: {
      bundleIdentifier: envConfig[env].bundleId
    },
    android: {
      package: envConfig[env].bundleId
    }
  }
}

25. Jak wdrażać aplikację do sklepów?

Wdrożenie obejmuje konfigurację build, metadane i proces zgłaszania.

bash
# Option 1: EAS Build (Expo)
# Installation
npm install -g eas-cli

# Configuration
eas build:configure

# eas.json
{
  "cli": {
    "version": ">= 5.0.0"
  },
  "build": {
    "development": {
      "developmentClient": true,
      "distribution": "internal"
    },
    "preview": {
      "distribution": "internal",
      "android": {
        "buildType": "apk"
      }
    },
    "production": {
      "autoIncrement": true
    }
  },
  "submit": {
    "production": {
      "ios": {
        "appleId": "your@email.com",
        "ascAppId": "1234567890"
      },
      "android": {
        "serviceAccountKeyPath": "./google-services.json",
        "track": "production"
      }
    }
  }
}

# Production build
eas build --platform all --profile production

# Store submission
eas submit --platform all --profile production

# Option 2: Fastlane (React Native CLI)
# Gemfile
source "https://rubygems.org"
gem "fastlane"

# ios/fastlane/Fastfile
default_platform(:ios)

platform :ios do
  desc "Deploy to TestFlight"
  lane :beta do
    increment_build_number(xcodeproj: "MyApp.xcodeproj")
    build_app(scheme: "MyApp")
    upload_to_testflight
  end

  desc "Deploy to App Store"
  lane :release do
    increment_build_number(xcodeproj: "MyApp.xcodeproj")
    build_app(scheme: "MyApp")
    upload_to_app_store(
      skip_screenshots: true,
      skip_metadata: true
    )
  end
end

# android/fastlane/Fastfile
default_platform(:android)

platform :android do
  desc "Deploy to Play Store internal"
  lane :beta do
    gradle(task: "clean bundleRelease")
    upload_to_play_store(
      track: "internal",
      aab: "app/build/outputs/bundle/release/app-release.aab"
    )
  end
end

26. Jak wdrażać aktualizacje OTA?

Aktualizacje Over-The-Air pozwalają wgrywać kod JavaScript bez konieczności przechodzenia przez sklepy z aplikacjami.

jsx
// With Expo Updates
import * as Updates from 'expo-updates'

async function checkForUpdates() {
  if (__DEV__) return // Not in development

  try {
    const update = await Updates.checkForUpdateAsync()

    if (update.isAvailable) {
      // Download update
      await Updates.fetchUpdateAsync()

      // Ask user to restart
      Alert.alert(
        'Update available',
        'A new version is available. Restart now?',
        [
          { text: 'Later', style: 'cancel' },
          {
            text: 'Restart',
            onPress: () => Updates.reloadAsync()
          }
        ]
      )
    }
  } catch (error) {
    console.error('Update check error:', error)
  }
}

// Automatic check on startup
function App() {
  useEffect(() => {
    checkForUpdates()
  }, [])

  return <AppNavigator />
}

// eas.json configuration for update channels
{
  "build": {
    "production": {
      "channel": "production"
    },
    "preview": {
      "channel": "preview"
    }
  }
}

// Command to publish an update
// eas update --branch production --message "Bug fix"

// With CodePush (Microsoft)
import codePush from 'react-native-code-push'

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

function App() {
  return <AppNavigator />
}

export default codePush(codePushOptions)(App)

Pytania zaawansowane

27. Jak optymalizować czas uruchamiania aplikacji?

Czas uruchamiania ma kluczowe znaczenie dla doświadczenia użytkownika.

1. Enable Hermes (optimized JS engine)jsx
// android/app/build.gradle
project.ext.react = [
    enableHermes: true
]

// ios/Podfile
:hermes_enabled => true

// 2. Lazy loading screens
const HeavyScreen = React.lazy(() => import('./HeavyScreen'))

// 3. Defer non-critical initializations
import { InteractionManager } from 'react-native'

function App() {
  useEffect(() => {
    // Execute after first render
    InteractionManager.runAfterInteractions(() => {
      // Initialize analytics
      Analytics.init()
      // Prefetch data
      prefetchCriticalData()
    })
  }, [])

  return <AppNavigator />
}

// 4. Optimize splash screen
import * as SplashScreen from 'expo-splash-screen'

// Prevent automatic hiding
SplashScreen.preventAutoHideAsync()

function App() {
  const [appIsReady, setAppIsReady] = useState(false)

  useEffect(() => {
    async function prepare() {
      try {
        // Load critical resources
        await Font.loadAsync(customFonts)
        await Image.prefetch(criticalImages)
        // Restore authentication
        await restoreAuth()
      } catch (e) {
        console.warn(e)
      } finally {
        setAppIsReady(true)
      }
    }

    prepare()
  }, [])

  const onLayoutRootView = useCallback(async () => {
    if (appIsReady) {
      // Hide splash screen
      await SplashScreen.hideAsync()
    }
  }, [appIsReady])

  if (!appIsReady) return null

  return (
    <View style={{ flex: 1 }} onLayout={onLayoutRootView}>
      <AppNavigator />
    </View>
  )
}

// 5. Inline requires to defer imports
// metro.config.js
module.exports = {
  transformer: {
    inlineRequires: true
  }
}

// Manual usage
function loadHeavyModule() {
  const HeavyModule = require('./HeavyModule').default
  return HeavyModule
}

28. Jak zadbać o bezpieczeństwo aplikacji React Native?

Bezpieczeństwo mobilne wymaga wielu warstw ochrony.

1. Secure storage for sensitive datajsx
import * as SecureStore from 'expo-secure-store'
// or
import * as Keychain from 'react-native-keychain'

async function saveToken(token) {
  await SecureStore.setItemAsync('auth_token', token)
}

async function getToken() {
  return await SecureStore.getItemAsync('auth_token')
}

// 2. Certificate pinning for network calls
// android/app/src/main/res/xml/network_security_config.xml
/*
<network-security-config>
  <domain-config>
    <domain includeSubdomains="true">api.myapp.com</domain>
    <pin-set>
      <pin digest="SHA-256">AAAAAA...</pin>
      <pin digest="SHA-256">BBBBBB...</pin>
    </pin-set>
  </domain-config>
</network-security-config>
*/

// 3. Jailbreak/root detection
import JailMonkey from 'jail-monkey'

function SecurityCheck() {
  useEffect(() => {
    if (JailMonkey.isJailBroken()) {
      Alert.alert(
        'Insecure device',
        'This application cannot run on a rooted/jailbroken device'
      )
    }
  }, [])
}

// 4. Code obfuscation
// metro.config.js (for Hermes)
module.exports = {
  transformer: {
    minifierConfig: {
      mangle: true,
      output: {
        ascii_only: true
      }
    }
  }
}

// 5. Screenshot/recording protection
import { usePreventScreenCapture } from 'expo-screen-capture'

function SensitiveScreen() {
  usePreventScreenCapture() // iOS only

  return <View>{/* Sensitive data */}</View>
}

// 6. Session timeout
function useSessionTimeout(timeoutMs = 5 * 60 * 1000) {
  const lastActivity = useRef(Date.now())
  const { logout } = useAuth()

  useEffect(() => {
    const subscription = AppState.addEventListener('change', (state) => {
      if (state === 'active') {
        const elapsed = Date.now() - lastActivity.current
        if (elapsed > timeoutMs) {
          logout()
        }
      } else {
        lastActivity.current = Date.now()
      }
    })

    return () => subscription.remove()
  }, [timeoutMs, logout])
}

29. Jak wdrożyć dostępność?

Dostępność jest niezbędna, by aplikacja była użyteczna dla wszystkich użytkowników.

jsx
import { AccessibilityInfo } from 'react-native'

// 1. Basic accessibility props
function AccessibleButton({ label, onPress, disabled }) {
  return (
    <TouchableOpacity
      onPress={onPress}
      disabled={disabled}
      accessible={true}
      accessibilityLabel={label}
      accessibilityRole="button"
      accessibilityState={{ disabled }}
      accessibilityHint={`Tap to ${label.toLowerCase()}`}
    >
      <Text>{label}</Text>
    </TouchableOpacity>
  )
}

// 2. Group elements for screen readers
function ProductCard({ product }) {
  return (
    <View
      accessible={true}
      accessibilityLabel={`${product.name}, ${product.price} dollars`}
    >
      <Image
        source={{ uri: product.image }}
        accessibilityIgnoresInvertColors={true}
      />
      <Text>{product.name}</Text>
      <Text>${product.price}</Text>
    </View>
  )
}

// 3. Announce dynamic changes
function NotificationBadge({ count }) {
  useEffect(() => {
    if (count > 0) {
      AccessibilityInfo.announceForAccessibility(
        `${count} new notification${count > 1 ? 's' : ''}`
      )
    }
  }, [count])

  return (
    <View accessibilityLabel={`${count} notifications`}>
      <Text>{count}</Text>
    </View>
  )
}

// 4. Detect accessibility preferences
function useAccessibilityPreferences() {
  const [isScreenReaderEnabled, setIsScreenReaderEnabled] = useState(false)
  const [isReduceMotionEnabled, setIsReduceMotionEnabled] = useState(false)

  useEffect(() => {
    AccessibilityInfo.isScreenReaderEnabled().then(setIsScreenReaderEnabled)
    AccessibilityInfo.isReduceMotionEnabled().then(setIsReduceMotionEnabled)

    const screenReaderListener = AccessibilityInfo.addEventListener(
      'screenReaderChanged',
      setIsScreenReaderEnabled
    )
    const reduceMotionListener = AccessibilityInfo.addEventListener(
      'reduceMotionChanged',
      setIsReduceMotionEnabled
    )

    return () => {
      screenReaderListener.remove()
      reduceMotionListener.remove()
    }
  }, [])

  return { isScreenReaderEnabled, isReduceMotionEnabled }
}

// 5. Adapt animations based on preferences
function AnimatedComponent() {
  const { isReduceMotionEnabled } = useAccessibilityPreferences()

  const animation = useSharedValue(0)

  useEffect(() => {
    animation.value = withTiming(1, {
      duration: isReduceMotionEnabled ? 0 : 300
    })
  }, [isReduceMotionEnabled])

  return <Animated.View style={animatedStyle} />
}

30. Jak zorganizować duży projekt React Native?

Przejrzysta architektura ułatwia utrzymanie kodu i jego skalowanie.

text
// Recommended structure
src/
├── app/                    # App configuration
│   ├── App.tsx
│   ├── Navigation.tsx
│   └── Providers.tsx
├── features/               # Feature modules
│   ├── auth/
│   │   ├── screens/
│   │   │   ├── LoginScreen.tsx
│   │   │   └── RegisterScreen.tsx
│   │   ├── components/
│   │   │   └── AuthForm.tsx
│   │   ├── hooks/
│   │   │   └── useAuth.ts
│   │   ├── services/
│   │   │   └── authService.ts
│   │   └── index.ts        # Public export
│   │
│   ├── products/
│   │   ├── screens/
│   │   ├── components/
│   │   ├── hooks/
│   │   └── services/
│   │
│   └── cart/
│       └── ...
├── shared/                 # Shared code
│   ├── components/
│   │   ├── Button.tsx
│   │   ├── Input.tsx
│   │   └── Card.tsx
│   ├── hooks/
│   │   ├── useDebounce.ts
│   │   └── useNetworkStatus.ts
│   ├── utils/
│   │   ├── format.ts
│   │   └── validation.ts
│   └── types/
│       └── index.ts
├── services/               # Global services
│   ├── api/
│   │   ├── client.ts
│   │   └── interceptors.ts
│   ├── storage/
│   │   └── secureStorage.ts
│   └── analytics/
│       └── analytics.ts
├── store/                  # Global state
│   ├── slices/
│   └── index.ts
└── theme/                  # Design system
    ├── colors.ts
    ├── typography.ts
    ├── spacing.ts
    └── index.ts
jsx
// Example feature module organization
// features/products/index.ts
export { ProductListScreen } from './screens/ProductListScreen'
export { ProductDetailScreen } from './screens/ProductDetailScreen'
export { useProducts } from './hooks/useProducts'
export { ProductCard } from './components/ProductCard'

// features/products/hooks/useProducts.ts
import { useQuery } from '@tanstack/react-query'
import { productService } from '../services/productService'

export function useProducts(categoryId?: string) {
  return useQuery({
    queryKey: ['products', categoryId],
    queryFn: () => productService.getProducts(categoryId)
  })
}

// features/products/services/productService.ts
import { apiClient } from '@/services/api/client'
import { Product } from '../types'

export const productService = {
  async getProducts(categoryId?: string): Promise<Product[]> {
    const params = categoryId ? { category: categoryId } : {}
    const response = await apiClient.get('/products', { params })
    return response.data
  },

  async getProduct(id: string): Promise<Product> {
    const response = await apiClient.get(`/products/${id}`)
    return response.data
  }
}

// Import alias configuration
// babel.config.js
module.exports = {
  presets: ['module:@react-native/babel-preset'],
  plugins: [
    [
      'module-resolver',
      {
        alias: {
          '@': './src',
          '@features': './src/features',
          '@shared': './src/shared',
          '@services': './src/services'
        }
      }
    ]
  ]
}

Podsumowanie

Trzydzieści przedstawionych pytań obejmuje kluczową wiedzę z React Native oczekiwaną podczas rozmów rekrutacyjnych. Najważniejsze obszary do opanowania to:

  • Architektura: zrozumienie mostu, JSI oraz nowej architektury
  • Komponenty: nawigacja, wydajne listy, animacje
  • Stan i dane: Zustand/Redux, React Query, lokalna pamięć
  • Wydajność: optymalizacja FlatList, memoizacja, profilowanie
  • Specyfika mobilna: uprawnienia, powiadomienia, deep links
  • Testowanie: Jest, Testing Library, Detox
  • Wdrożenie: EAS Build, sklepy, aktualizacje OTA
  • Bezpieczeństwo i dostępność: bezpieczne przechowywanie, zgodność z WCAG

Przygotowanie do rozmowy z React Native wymaga zrozumienia aspektów charakterystycznych dla świata mobilnego, wykraczających poza wiedzę o React webowym. Praktyka na realnych projektach oraz testowanie na fizycznych urządzeniach utrwala te koncepcje i przygotowuje kandydata do każdej rozmowy.

Zacznij ćwiczyć!

Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.

Tagi

#react native interview
#mobile interview
#react native questions
#javascript
#technical interview

Udostępnij

Powiązane artykuły