React Native i TypeScript w 2026: Typobezpieczna architektura i pytania rekrutacyjne

Tworzenie typobezpiecznych aplikacji React Native z TypeScriptem, Codegen, TurboModules i Strict TypeScript API. Obejmuje wzorce architektury, typowaną nawigację, wymagania toolchain dla 0.87 oraz pytania rekrutacyjne.

Diagramy architektury React Native TypeScript z kodem TypeScript i strukturami typów

React Native TypeScript stał się standardem branżowym w 2026 roku, a wersja 0.87 czyni Strict TypeScript API obowiązkowym domyślnym ustawieniem, kończąc proces modernizacji toolchain. TypeScript nie jest już opcjonalnym klejem między JavaScriptem a kodem natywnym: kontroluje cały kontrakt od propsów komponentów po interfejsy TurboModules, wyłapując niezgodności na etapie budowania zamiast w logach crashów produkcyjnych.

Co zmieniło się w 0.87

React Native 0.87 (sierpień 2026) czyni Strict TypeScript API obowiązkowym domyślnym ustawieniem. Podnosi również minimalne wymagania do Node.js 22.13+, Kotlin 2.0+ oraz Android Gradle Plugin 9. Możliwość wyłączenia dla starszych deep imports pozostaje dostępna tylko do wersji 0.88.

Dlaczego TypeScript jest standardem dla React Native

Każde wywołanie npx react-native init od wersji 0.76 tworzy projekt TypeScript. Prawdziwa zmiana nastąpiła jednak na granicy z kodem natywnym. Przed Codegen deweloperzy pisali ręczne asercje typów przy przekraczaniu granicy z JavaScript do Objective-C lub Kotlin, co było słabo typowanym kontraktem psującym się cicho w runtime. Codegen czyta pliki specyfikacji TypeScript i automatycznie generuje interfejsy C++, Objective-C++ oraz Java/Kotlin. Jeśli specyfikacja TypeScript deklaruje metodę zwracającą number, wygenerowany interfejs natywny wymusza to ograniczenie na etapie kompilacji.

Dokumentacja React Native opisuje podstawową konfigurację, ale wzorce typobezpieczeństwa istotne w produkcji idą dalej: typowane stosy nawigacji, generyczne hooki API, unie dyskryminowane dla maszyn stanów oraz specyfikacje TurboModules sterowane przez Codegen.

Strict TypeScript API: Od opcji do domyślnego ustawienia

Strict TypeScript API zostało wprowadzone w React Native 0.80 jako funkcja opt-in. Od wersji 0.87 jest obowiązkowym domyślnym ustawieniem dla wszystkich nowych projektów. Typy są generowane bezpośrednio z kodu źródłowego React Native zamiast być utrzymywane ręcznie, eliminując rozbieżności między dokumentowanym API a rzeczywistą implementacją.

tsconfig.json (domyślna konfiguracja 0.87+)json
{
  "extends": "@react-native/typescript-config",
  "compilerOptions": {
    "strict": true,
    "exactOptionalPropertyTypes": true,
    "noUncheckedIndexedAccess": true
  }
}

Przy tej konfiguracji importowanie czegokolwiek ze ścieżki podrzędnej jak react-native/Libraries/Text/Text powoduje błąd typu. Wszystkie importy muszą pochodzić z głównego pakietu react-native. Jeśli biblioteka lub starsza baza kodu nadal polega na deep imports, tymczasowe wyłączenie jest dostępne do wersji 0.88:

json
{
  "extends": "@react-native/typescript-config",
  "compilerOptions": {
    "customConditions": ["react-native", "react-native-legacy-deep-imports"]
  }
}

To wyłączenie zostanie usunięte w wersji 0.89. Migracja powinna nastąpić wcześniej.

Wymagania toolchain w 0.87

React Native 0.87 podnosi minimalne wersje w całym łańcuchu budowania:

KomponentMinimum
Node.js22.13.0
Kotlin2.0 (dołączona: 2.2.0)
Android Gradle Plugin9.x
Android compileSdk34 (min), 37 (target)

Dla Androida AGP 9 wymaga dodatkowej konfiguracji:

properties
# android/gradle.properties
android.builtInKotlin=false
android.newDsl=false

Te wymagania dotyczą wszystkich projektów celujących w 0.87+. Przewodnik migracji do New Architecture opisuje szerszy kontekst tych zmian.

Typobezpieczna nawigacja z React Navigation 7

React Navigation 7.x zapewnia pierwszorzędne wsparcie TypeScript. Kluczowy wzorzec: zdefiniowanie typu RootStackParamList, który mapuje każdą nazwę ekranu na oczekiwane parametry, a następnie propagowanie tego typu przez nawigatory i komponenty ekranów.

navigation/types.tstypescript
export type RootStackParamList = {
  Home: undefined;
  Profile: { userId: string };
  Settings: undefined;
  ArticleDetail: { articleId: string; source: 'feed' | 'search' };
};

export type AppTabParamList = {
  Dashboard: undefined;
  Explore: { category?: string };
  Notifications: undefined;
};

Komponenty ekranów otrzymują następnie typowane propsy bez ręcznego rzutowania:

screens/ArticleDetailScreen.tsxtypescript
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
import type { RootStackParamList } from '../navigation/types';

type Props = NativeStackScreenProps<RootStackParamList, 'ArticleDetail'>;

export function ArticleDetailScreen({ route, navigation }: Props) {
  // route.params.articleId is string, guaranteed by the type
  // route.params.source is 'feed' | 'search', no runtime check needed
  const { articleId, source } = route.params;

  // navigation.navigate('Profile', { userId: '123' }) is type-checked
  // navigation.navigate('Profile', {}) causes a compile error: missing userId
  return (
    <ArticleView id={articleId} referrer={source} />
  );
}

To eliminuje całą klasę błędów runtime: nawigacja do ekranu z błędnymi lub brakującymi parametrami kończy się niepowodzeniem na etapie budowania.

Typowanie hooka useNavigation

Dla komponentów, które nie są bezpośrednimi dziećmi ekranu, użycie useNavigation<NativeStackNavigationProp<RootStackParamList>>() zapewnia to samo bezpieczeństwo typów bez prop drillingu.

Budowanie typobezpiecznych TurboModules z Codegen

TurboModules zastępują stary system Native Modules. Plik specyfikacji TypeScript służy jako jedyne źródło prawdy, a Codegen generuje z niego interfejsy natywne. Jeśli specyfikacja i implementacja natywna się rozmijają, budowanie kończy się niepowodzeniem.

specs/NativeDeviceInfo.tstypescript
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';

export interface Spec extends TurboModule {
  getDeviceModel(): string;
  getBatteryLevel(): Promise<number>;
  getStorageInfo(): Promise<{
    totalBytes: number;
    freeBytes: number;
    usedPercentage: number;
  }>;
  onBatteryChange(callback: (level: number) => void): void;
}

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

Uruchomienie npx react-native codegen generuje odpowiednie interfejsy C++, Objective-C++ oraz Java. Implementacja natywna musi dokładnie odpowiadać każdej sygnaturze metody. Na przykład getStorageInfo musi zwracać obiekt z trzema polami numerycznymi; zwrócenie innego kształtu powoduje błąd kompilacji po stronie natywnej.

android/app/src/main/java/com/app/DeviceInfoModule.ktkotlin
class DeviceInfoModule(reactContext: ReactApplicationContext) :
    NativeDeviceInfoSpec(reactContext) {

    // Return type enforced by generated NativeDeviceInfoSpec
    override fun getDeviceModel(): String {
        return Build.MODEL
    }

    override fun getBatteryLevel(): Promise<Double> {
        val bm = reactContext.getSystemService(Context.BATTERY_SERVICE)
            as BatteryManager
        val level = bm.getIntProperty(
            BatteryManager.BATTERY_PROPERTY_CAPACITY
        ).toDouble()
        return Promise.resolve(level)
    }
}

To podejście eliminuje parsowanie ReadableMap i NSDictionary, które powodowało ciche błędy koercji typów w starej architekturze. Głębsze spojrzenie na system modułów natywnych znajduje się w pytaniach rekrutacyjnych o native modules.

Gotowy na rozmowy o React Native?

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

Generyczne pobieranie danych z typowanymi hookami API

Wzorzec wielokrotnego użytku typowanego hooka pozwala uniknąć duplikacji logiki fetch między ekranami przy zachowaniu pełnej inferencji typów:

hooks/useApiQuery.tstypescript
import { useQuery, UseQueryOptions } from '@tanstack/react-query';

interface ApiResponse<T> {
  data: T;
  meta: { page: number; totalPages: number };
}

export function useApiQuery<T>(
  key: readonly string[],
  endpoint: string,
  options?: Omit<UseQueryOptions<ApiResponse<T>>, 'queryKey' | 'queryFn'>
) {
  return useQuery<ApiResponse<T>>({
    queryKey: key,
    queryFn: async () => {
      const response = await fetch(`${API_BASE}${endpoint}`);
      if (!response.ok) throw new ApiError(response.status);
      return response.json() as Promise<ApiResponse<T>>;
    },
    ...options,
  });
}

// Usage, T is inferred as Article[]
interface Article {
  id: string;
  title: string;
  publishedAt: string;
}

const { data, isLoading } = useApiQuery<Article[]>(
  ['articles', 'latest'],
  '/articles?sort=latest'
);
// data.data is Article[], fully typed
// data.meta.totalPages is number

Parametr generyczny T przepływa przez cały łańcuch: od miejsca wywołania hooka, przez funkcję zapytania, do komponentu konsumującego wynik. Żadnych rzutowań as, żadnych typów any.

Unie dyskryminowane dla maszyn stanów

Złożone stany ekranu (ładowanie, błąd, pusty, załadowany) najlepiej modelować jako unie dyskryminowane zamiast zbioru opcjonalnych pól:

types/screen-state.tstypescript
type ScreenState<T> =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'error'; error: string; retryCount: number }
  | { status: 'empty'; message: string }
  | { status: 'loaded'; data: T; refreshedAt: Date };

// components/DataScreen.tsx
function renderContent<T>(state: ScreenState<T>, renderItem: (data: T) => ReactNode) {
  switch (state.status) {
    case 'idle':
      return null;
    case 'loading':
      return <LoadingSpinner />;
    case 'error':
      // state.error is string here, TypeScript narrows automatically
      return <ErrorBanner message={state.error} retries={state.retryCount} />;
    case 'empty':
      return <EmptyState message={state.message} />;
    case 'loaded':
      // state.data is T, fully typed
      return renderItem(state.data);
  }
}

Ten wzorzec sprawia, że niemożliwe stany są niereprezentowalne. Stan loading nie może przypadkowo zawierać przestarzałych data, a stan error zawsze zawiera kontekst do debugowania.

Unikanie częściowych obiektów stanu

Częsty anty-wzorzec: { isLoading: boolean; error?: string; data?: T }. Pozwala to na stany takie jak { isLoading: true, error: 'fail', data: [...] }, trzy sprzeczne sygnały jednocześnie. Unie dyskryminowane zapobiegają temu na poziomie typów.

Pytania rekrutacyjne React Native TypeScript

Te pytania odzwierciedlają to, o co pytają zespoły senior mobile engineering na rozmowach kwalifikacyjnych w 2026 roku, gdy New Architecture i TypeScript są standardem.

Jak Codegen wymusza bezpieczeństwo typów na granicy JavaScript-natywny?

Codegen czyta pliki specyfikacji TypeScript (lub Flow) i generuje kod interfejsów C++, Objective-C++ oraz Java/Kotlin. Wygenerowane interfejsy natywne wymuszają dokładne sygnatury metod, typy parametrów i typy zwracane zdefiniowane w specyfikacji. Jeśli implementacja natywna odbiega (zwraca Int gdzie specyfikacja deklaruje Double, lub pomija pole w strukturze), kompilator natywny odrzuca budowanie. To przenosi błędy typów z crashów runtime na niepowodzenia budowania.

Czym jest Strict TypeScript API i dlaczego jest ważne?

Strict TypeScript API generuje typy bezpośrednio z kodu źródłowego React Native zamiast utrzymywania ręcznie pisanych plików .d.ts. W wersji 0.87 stało się obowiązkowym domyślnym ustawieniem. Ogranicza importy do głównego pakietu react-native, deprecjonując deep imports. Definiuje to stabilną publiczną powierzchnię API: wewnętrzne refaktoryzacje nie mogą złamać kodu konsumenta, jeśli konsumenci używają tylko strict types. Tymczasowe wyłączenie przez customConditions pozostaje dostępne do wersji 0.88.

Jak typować parametry React Navigation między zagnieżdżonymi nawigatorami?

Należy zdefiniować typ ParamList dla każdego nawigatora i złożyć je używając NavigatorScreenParams. Dla nawigatora tab zagnieżdżonego wewnątrz stack, lista parametrów stack odwołuje się do tab: type RootStack = { Main: NavigatorScreenParams<TabParamList>; Modal: { id: string } }. Każde wywołanie navigate() jest następnie sprawdzane pod kątem typów przez całą hierarchię zagnieżdżenia, wyłapując błędne nazwy ekranów lub brakujące parametry na etapie kompilacji.

Jaki problem rozwiązują unie dyskryminowane w zarządzaniu stanem React Native?

Unie dyskryminowane modelują wzajemnie wykluczające się stany (ładowanie, błąd, załadowany) jako oddzielne gałęzie typu unii, kluczowane przez pole status. TypeScript zawęża typ w każdej gałęzi instrukcji switch, więc dostęp do state.data jest możliwy tylko gdy state.status === 'loaded'. To zapobiega niemożliwym stanom jak wskaźnik ładowania pokazywany obok danych błędu, klasie błędów której opcjonalne pola i flagi boolowskie nie mogą zapobiec.

Wyjaśnij różnicę między TurboModules a starym systemem Native Modules.

Native Modules komunikowały się przez asynchroniczny bridge, serializując wszystkie dane do JSON. TurboModules używają JSI (JavaScript Interface) dla synchronicznych, bezpośrednich wywołań C++ bez narzutu serializacji. Ładują się również leniwie (przy pierwszym użyciu zamiast przy starcie aplikacji, redukując czas zimnego startu) i używają Codegen do generowania typobezpiecznych interfejsów ze specyfikacji TypeScript. Stary system polegał na parsowaniu ReadableMap / NSDictionary z koercją typów w runtime; TurboModules wymuszają typy na etapie kompilacji.

Jakie API zostały usunięte w React Native 0.87?

Wersja 0.87 usunęła InteractionManager (należy używać requestIdleCallback), typy NativeMethods / NativeMethodsMixin (należy używać HostInstance) oraz kilka propsów StatusBar. Deep imports do ścieżek src/private/ są teraz błędami typów. Flaga useTurboModules również została usunięta, ponieważ TurboModules są teraz zawsze włączone.

Więcej pytań rekrutacyjnych React Native w kompletnym przewodniku obejmującym architekturę, wydajność i debugowanie.

Zacznij ćwiczyć!

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

Źródła

Co zapamiętać o React Native TypeScript w 2026

  • Strict TypeScript API (obowiązkowe w 0.87) ogranicza importy do stabilnej publicznej powierzchni, zapobiegając uszkodzeniom przez wewnętrzne zmiany. Wyłączenie dla starszych deep imports jest dostępne tylko do wersji 0.88
  • Codegen generuje interfejsy natywne z plików specyfikacji TypeScript, przenosząc błędy typów z crashów runtime na niepowodzenia budowania przez granicę JS-natywny
  • Minimalne wymagania toolchain w 0.87: Node.js 22.13+, Kotlin 2.0+, AGP 9, compileSdk 34+
  • Typowane parametry nawigacji przez RootStackParamList i NativeStackScreenProps wyłapują błędne nazwy ekranów i brakujące parametry przed uruchomieniem aplikacji
  • Unie dyskryminowane modelują stany ekranu jako wzajemnie wykluczające się gałęzie, czyniąc niemożliwe stany niereprezentowalnymi na poziomie typów
  • TurboModules z typowanymi specyfikacjami zastępują stare parsowanie ReadableMap / NSDictionary, wymuszając pełne bezpieczeństwo typów od JavaScript przez C++ do kodu natywnego platformy
  • Generyczne hooki API z TanStack Query zachowują inferencję typów od endpointu do komponentu bez ręcznych rzutowań

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 25 sierpnia 2026

Tagi

#react-native
#typescript
#mobile-development
#new-architecture
#turbomodules

Udostępnij

Powiązane artykuły