Tworzenie Aplikacji React Native w 2026: Kompletny Przewodnik i Pytania Rekrutacyjne

Poznaj tworzenie aplikacji React Native w 2026 roku z Nową Architekturą, JSI, Fabric i TurboModules. Praktyczny tutorial z przykładami kodu i pytaniami na rozmowę kwalifikacyjną.

Tworzenie Aplikacji React Native w 2026: Kompletny Przewodnik i Pytania Rekrutacyjne

Tworzenie aplikacji React Native w 2026 roku przeszło fundamentalną transformację wraz z wersją 0.87, która wprowadza Nową Architekturę jako jedyną obsługiwaną opcję. Stary bridge, który serializował każde wywołanie JavaScript-do-native jako JSON, został całkowicie usunięty i zastąpiony przez JSI (JavaScript Interface) umożliwiający bezpośrednią, synchroniczną komunikację.

Co się zmieniło w React Native 0.87

React Native 0.87 czyni Nową Architekturę obowiązkową. Stary bridge został całkowicie usunięty. Renderowanie złożonych list działa o 43% szybciej, spadki klatek podczas przewijania zmniejszyły się o 95%, a wykorzystanie pamięci spadło o 33% w testach wydajnościowych.

Zrozumienie JSI i Nowej Architektury

JSI (JavaScript Interface) zastępuje asynchroniczny bridge bezpośrednimi wiązaniami C++ między JavaScript a kodem natywnym. Zamiast serializować dane do JSON, przekazywać je przez kolejkę komunikatów i deserializować po drugiej stronie, JSI pozwala JavaScript przechowywać referencje do obiektów natywnych i wywoływać ich metody bezpośrednio.

Ta zmiana architektoniczna umożliwia trzy kluczowe możliwości: synchroniczne wywołania natywne, współdzielone posiadanie obiektów między warstwami JavaScript i natywną oraz leniwe ładowanie modułów natywnych.

NativeUserModule.tstypescript
import { TurboModuleRegistry, TurboModule } from 'react-native';

export interface Spec extends TurboModule {
  getUserProfile(userId: string): Promise<{
    id: string;
    name: string;
    email: string;
  }>;
  
  // Wywołanie synchroniczne - możliwe tylko z JSI
  getDeviceLocale(): string;
}

export default TurboModuleRegistry.getEnforcing<Spec>('UserModule');

Metoda getDeviceLocale() zwraca wynik natychmiast bez oczekiwania na Promise. Było to niemożliwe ze starym bridge, gdzie każde wywołanie natywne wymagało asynchronicznej serializacji.

Budowanie Komponentu Produkcyjnego z Fabric

Fabric to nowy system renderowania działający obok JSI. Zarządza drzewem UI w C++ i synchronizuje się bezpośrednio z wątkiem JavaScript, eliminując "zacinanie się UI", które występowało gdy stary bridge pozostawał w tyle podczas szybkich aktualizacji.

ProductCard.tsxtypescript
import React, { memo, useCallback } from 'react';
import {
  View,
  Text,
  Image,
  Pressable,
  StyleSheet,
} from 'react-native';
import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withSpring,
} from 'react-native-reanimated';

interface Product {
  id: string;
  name: string;
  price: number;
  imageUrl: string;
  inStock: boolean;
}

interface ProductCardProps {
  product: Product;
  onPress: (id: string) => void;
}

const AnimatedPressable = Animated.createAnimatedComponent(Pressable);

export const ProductCard = memo(function ProductCard({
  product,
  onPress,
}: ProductCardProps) {
  const scale = useSharedValue(1);

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

  const handlePressIn = useCallback(() => {
    scale.value = withSpring(0.95);
  }, [scale]);

  const handlePressOut = useCallback(() => {
    scale.value = withSpring(1);
  }, [scale]);

  const handlePress = useCallback(() => {
    onPress(product.id);
  }, [onPress, product.id]);

  return (
    <AnimatedPressable
      style={[styles.card, animatedStyle]}
      onPressIn={handlePressIn}
      onPressOut={handlePressOut}
      onPress={handlePress}
      accessibilityRole="button"
      accessibilityLabel={`${product.name}, ${product.price} złotych`}
    >
      <Image
        source={{ uri: product.imageUrl }}
        style={styles.image}
        resizeMode="cover"
      />
      <View style={styles.content}>
        <Text style={styles.name} numberOfLines={2}>
          {product.name}
        </Text>
        <Text style={styles.price}>{product.price.toFixed(2)}</Text>
        {!product.inStock && (
          <Text style={styles.outOfStock}>Brak w magazynie</Text>
        )}
      </View>
    </AnimatedPressable>
  );
});

const styles = StyleSheet.create({
  card: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    overflow: 'hidden',
    marginHorizontal: 8,
    marginVertical: 4,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
    elevation: 3,
  },
  image: {
    width: '100%',
    height: 160,
  },
  content: {
    padding: 12,
  },
  name: {
    fontSize: 16,
    fontWeight: '600',
    color: '#1a1a1a',
    marginBottom: 4,
  },
  price: {
    fontSize: 18,
    fontWeight: '700',
    color: '#2563eb',
  },
  outOfStock: {
    fontSize: 12,
    color: '#dc2626',
    marginTop: 4,
  },
});

Ten komponent używa react-native-reanimated, który uruchamia animacje na wątku UI przez JSI. Hooki useSharedValue i useAnimatedStyle komunikują się bezpośrednio z kodem natywnym bez przechodzenia przez bridge.

Zarządzanie Stanem z TanStack Query

Stan serwera w aplikacjach React Native korzysta z dedykowanych narzędzi. TanStack Query obsługuje cache'owanie, odświeżanie w tle i optymistyczne aktualizacje bez wymagania globalnego kontenera stanu.

hooks/useProducts.tstypescript
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

interface Product {
  id: string;
  name: string;
  price: number;
  imageUrl: string;
  inStock: boolean;
}

const API_BASE = 'https://api.example.com';

async function fetchProducts(category?: string): Promise<Product[]> {
  const url = category
    ? `${API_BASE}/products?category=${category}`
    : `${API_BASE}/products`;
  
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error('Nie udało się pobrać produktów');
  }
  return response.json();
}

export function useProducts(category?: string) {
  return useQuery({
    queryKey: ['products', category],
    queryFn: () => fetchProducts(category),
    staleTime: 5 * 60 * 1000, // 5 minut
    gcTime: 30 * 60 * 1000, // 30 minut
  });
}

export function useToggleStock() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: async (productId: string) => {
      const response = await fetch(`${API_BASE}/products/${productId}/toggle-stock`, {
        method: 'POST',
      });
      if (!response.ok) {
        throw new Error('Nie udało się zaktualizować stanu magazynowego');
      }
      return response.json();
    },
    // Optymistyczna aktualizacja
    onMutate: async (productId) => {
      await queryClient.cancelQueries({ queryKey: ['products'] });
      
      const previousProducts = queryClient.getQueryData<Product[]>(['products']);
      
      queryClient.setQueryData<Product[]>(['products'], (old) =>
        old?.map((p) =>
          p.id === productId ? { ...p, inStock: !p.inStock } : p
        )
      );
      
      return { previousProducts };
    },
    onError: (_err, _productId, context) => {
      if (context?.previousProducts) {
        queryClient.setQueryData(['products'], context.previousProducts);
      }
    },
    onSettled: () => {
      queryClient.invalidateQueries({ queryKey: ['products'] });
    },
  });
}

Callback onMutate wykonuje optymistyczną aktualizację przed odpowiedzią serwera. Jeśli mutacja się nie powiedzie, onError przywraca poprzedni stan. Ten wzorzec zapewnia natychmiastową reakcję UI przy zachowaniu spójności danych.

Gotowy na rozmowy o React Native?

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

Nawigacja z Expo Router

Expo Router zapewnia nawigację opartą na plikach dla React Native, podobnie jak Next.js App Router. Struktura plików automatycznie definiuje trasy.

app/(tabs)/_layout.tsxtypescript
import { Tabs } from 'expo-router';
import { Home, Search, ShoppingCart, User } from 'lucide-react-native';

export default function TabsLayout() {
  return (
    <Tabs
      screenOptions={{
        tabBarActiveTintColor: '#2563eb',
        tabBarInactiveTintColor: '#6b7280',
        headerShown: false,
      }}
    >
      <Tabs.Screen
        name="index"
        options={{
          title: 'Główna',
          tabBarIcon: ({ color, size }) => (
            <Home color={color} size={size} />
          ),
        }}
      />
      <Tabs.Screen
        name="search"
        options={{
          title: 'Szukaj',
          tabBarIcon: ({ color, size }) => (
            <Search color={color} size={size} />
          ),
        }}
      />
      <Tabs.Screen
        name="cart"
        options={{
          title: 'Koszyk',
          tabBarIcon: ({ color, size }) => (
            <ShoppingCart color={color} size={size} />
          ),
        }}
      />
      <Tabs.Screen
        name="profile"
        options={{
          title: 'Profil',
          tabBarIcon: ({ color, size }) => (
            <User color={color} size={size} />
          ),
        }}
      />
    </Tabs>
  );
}

Dynamiczne trasy używają notacji nawiasów kwadratowych. Plik w app/product/[id].tsx obsługuje dowolną ścieżkę /product/123:

app/product/[id].tsxtypescript
import { useLocalSearchParams } from 'expo-router';
import { View, Text, ActivityIndicator } from 'react-native';
import { useQuery } from '@tanstack/react-query';

export default function ProductDetailScreen() {
  const { id } = useLocalSearchParams<{ id: string }>();

  const { data: product, isLoading, error } = useQuery({
    queryKey: ['product', id],
    queryFn: () => fetchProduct(id),
    enabled: !!id,
  });

  if (isLoading) {
    return (
      <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
        <ActivityIndicator size="large" color="#2563eb" />
      </View>
    );
  }

  if (error || !product) {
    return (
      <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
        <Text>Nie znaleziono produktu</Text>
      </View>
    );
  }

  return (
    <View style={{ flex: 1, padding: 16 }}>
      <Text style={{ fontSize: 24, fontWeight: 'bold' }}>{product.name}</Text>
      <Text style={{ fontSize: 20, color: '#2563eb' }}>
        {product.price.toFixed(2)}      </Text>
    </View>
  );
}

Popularne Pytania Rekrutacyjne o Architekturę React Native

Rozmowy kwalifikacyjne na stanowiska React Native w 2026 roku koncentrują się głównie na Nowej Architekturze. Poniższe pytania pojawiają się często.

Obszary Fokusowe na Rozmowach

Rekruterzy oczekują, że kandydaci potrafią wyjaśnić JSI, Fabric i TurboModules na poziomie koncepcyjnym. Zapamiętywanie składni jest mniej istotne niż zrozumienie, dlaczego architektura się zmieniła i jakie problemy rozwiązuje.

Pytanie: Czym jest JSI i dlaczego React Native go przyjął?

JSI (JavaScript Interface) to warstwa C++, która pozwala JavaScript przechowywać referencje do obiektów natywnych i bezpośrednio wywoływać ich metody. Stary bridge serializował każde wywołanie do JSON, kolejkował je asynchronicznie i deserializował po stronie natywnej. To dodawało opóźnienia i uniemożliwiało operacje synchroniczne.

Dzięki JSI funkcja JavaScript może wywołać metodę natywną i otrzymać wynik w tej samej klatce. Umożliwia to funkcje takie jak synchroniczne pomiary układu i bezpośrednia manipulacja widokami natywnymi.

Pytanie: Czym Fabric różni się od starego renderera?

Fabric to system renderowania w C++, który utrzymuje drzewo UI w kodzie natywnym zamiast w JavaScript. Stary renderer przechowywał shadow tree w JavaScript i wysyłał partie aktualizacji przez bridge.

Zalety Fabric:

  • Synchroniczne obliczenia układu przez JSI
  • Wsparcie dla concurrent rendering (funkcje React 18 działają poprawnie)
  • Lepsze zarządzanie pamięcią ze współdzielonym posiadaniem między JS a native
  • Zmniejszony narzut serializacji

Pytanie: Czym są TurboModules?

TurboModules to zamiennik Native Modules. Kluczowe różnice:

  1. Leniwe ładowanie: TurboModules ładują się dopiero przy pierwszym dostępie, skracając czas uruchamiania
  2. Bezpieczeństwo typów: CodeGen generuje type-safe wiązania ze specyfikacji TypeScript
  3. Metody synchroniczne: TurboModules mogą eksponować funkcje synchroniczne gdy jest to potrzebne
  4. Bezpośredni dostęp do JSI: Brak serializacji przez bridge

Pytanie: Kiedy użyć synchronicznej metody natywnej?

Synchroniczne metody natywne nadają się do operacji, które:

  • Kończą się w mikrosekundach (odczyt locale urządzenia, sprawdzenie statusu uprawnień)
  • Celowo blokują UI (potwierdzenie modalne przed nawigacją)
  • Zwracają wartości potrzebne do natychmiastowego renderowania

Należy unikać metod synchronicznych dla:

  • Żądań sieciowych
  • Operacji I/O na plikach wykraczających poza małe odczyty
  • Zapytań do bazy danych
  • Każdej operacji przekraczającej 16ms (blokuje klatkę)

Więcej pytań rekrutacyjnych o React Native znajduje się w module networking i API oraz sekcji modułów natywnych.

Testowanie Komponentów React Native

React Native Testing Library dostarcza narzędzia do testowania komponentów tak, jak użytkownicy z nimi interagują, skupiając się na dostępności i zachowaniu zamiast szczegółów implementacji.

__tests__/ProductCard.test.tsxtypescript
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react-native';
import { ProductCard } from '../components/ProductCard';

const mockProduct = {
  id: 'prod-1',
  name: 'Słuchawki Bezprzewodowe',
  price: 149.99,
  imageUrl: 'https://example.com/headphones.jpg',
  inStock: true,
};

describe('ProductCard', () => {
  it('wyświetla informacje o produkcie', () => {
    const onPress = jest.fn();
    render(<ProductCard product={mockProduct} onPress={onPress} />);

    expect(screen.getByText('Słuchawki Bezprzewodowe')).toBeOnTheScreen();
    expect(screen.getByText('149.99 zł')).toBeOnTheScreen();
  });

  it('wywołuje onPress z id produktu po naciśnięciu', () => {
    const onPress = jest.fn();
    render(<ProductCard product={mockProduct} onPress={onPress} />);

    fireEvent.press(
      screen.getByRole('button', { name: /słuchawki bezprzewodowe/i })
    );

    expect(onPress).toHaveBeenCalledWith('prod-1');
    expect(onPress).toHaveBeenCalledTimes(1);
  });

  it('pokazuje komunikat o braku gdy produkt niedostępny', () => {
    const outOfStockProduct = { ...mockProduct, inStock: false };
    render(<ProductCard product={outOfStockProduct} onPress={jest.fn()} />);

    expect(screen.getByText('Brak w magazynie')).toBeOnTheScreen();
  });
});

Testy odpytują według treści tekstowej i ról dostępności zamiast test ID lub wewnętrznych szczegółów komponentu. To podejście wyłapuje regresje wpływające na użytkowników, pozostając odpornym na refaktoryzację.

Monitorowanie Wydajności w Produkcji

React Native 0.87 integruje się z Sentry i podobnymi narzędziami do monitorowania wydajności produkcyjnej. Kluczowe metryki do śledzenia:

  • Time to Interactive (TTI): Jak długo do momentu gdy aplikacja reaguje na input użytkownika
  • Spadki klatek: Klatki renderowane dłużej niż 16.67ms
  • Wykorzystanie wątku JavaScript: Wysokie użycie wskazuje na kosztowne obliczenia blokujące UI
  • Wzrost pamięci: Stały wzrost sugeruje wycieki
app/_layout.tsxtypescript
import * as Sentry from '@sentry/react-native';
import { useEffect } from 'react';
import { AppState, AppStateStatus } from 'react-native';

Sentry.init({
  dsn: process.env.EXPO_PUBLIC_SENTRY_DSN,
  tracesSampleRate: 0.2,
  profilesSampleRate: 0.1,
  enableAutoPerformanceTracing: true,
});

export default function RootLayout() {
  useEffect(() => {
    const subscription = AppState.addEventListener(
      'change',
      (state: AppStateStatus) => {
        if (state === 'active') {
          Sentry.addBreadcrumb({
            category: 'app.lifecycle',
            message: 'Aplikacja stała się aktywna',
            level: 'info',
          });
        }
      }
    );

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

  // ... reszta layoutu
}

Kluczowe Wnioski dla Tworzenia Aplikacji React Native w 2026

  • Nowa Architektura (JSI, Fabric, TurboModules) jest obowiązkowa w React Native 0.87. Stary bridge już nie istnieje.
  • JSI umożliwia synchroniczne wywołania natywne. Należy ich używać oszczędnie dla operacji poniżej 16ms.
  • Fabric renderuje UI w C++ i synchronizuje z JavaScript przez JSI. To eliminuje asynchroniczne wąskie gardło renderowania.
  • TurboModules ładują się leniwie i dostarczają type-safe wiązania generowane ze specyfikacji TypeScript.
  • Expo Router oferuje nawigację opartą na plikach podobną do Next.js App Router.
  • React Native Testing Library testuje komponenty przez ich publiczne API i drzewo dostępności.
  • Aplikacje produkcyjne powinny śledzić TTI, spadki klatek i wykorzystanie pamięci od pierwszego dnia.

Dla głębszego spojrzenia na budowanie kompletnych aplikacji, zobacz tutorial kompletnej aplikacji React Native. Przewodnik po Nowej Architekturze obejmuje migrację ze starszych wersji.

Zacznij ćwiczyć!

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

Wyzwanie dnia

Znajdziesz błąd w React Native?

Prawdziwy fragment kodu, ukryty błąd, jedna próba dziennie. Bez konta, żeby spróbować.

Anthony Fillion-Maillet

Autor:

Anthony Fillion-Maillet

Założyciel SharpSkill

Programista fullstack od ponad 10 lat. Prowadzi SharpSkill i odpowiada za wszystko, co się tu ukazuje.

Zaktualizowano 6 września 2026

Udostępnij

Powiązane artykuły