React Native와 TypeScript 2026: 타입 안전한 아키텍처와 면접 질문

TypeScript로 타입 안전한 React Native 앱을 구축하는 방법을 다룹니다. Codegen, TurboModules, 0.87에서 필수화된 Strict TypeScript API, 아키텍처 패턴, 타입 안전 내비게이션, 툴체인 요구 사항, 면접 질문을 코드 예제와 함께 설명합니다.

React Native TypeScript 타입 안전 아키텍처와 코드, 모바일 디바이스

2026년 중반 현재, React Native에서 TypeScript 타입 안전성은 크게 성숙한 단계에 이르렀습니다. 버전 0.87에서는 Strict TypeScript API가 필수 기본값이 되었으며, 대규모 툴체인 현대화가 완료되었습니다. TypeScript는 더 이상 JavaScript와 네이티브 코드 사이를 연결하는 선택적 도구가 아닙니다. 컴포넌트 props에서 TurboModule 인터페이스에 이르기까지 애플리케이션 전체의 계약을 주도하며, 프로덕션 크래시 로그가 아닌 빌드 시점에 불일치를 감지하는 구조로 발전했습니다.

0.87에서 변경된 사항

React Native 0.87(2026년 8월)에서는 Strict TypeScript API가 필수 기본값이 되었습니다. 또한 최소 요구 사항이 Node.js 22.13 이상, Kotlin 2.0 이상, Android Gradle Plugin 9로 상향되었습니다. 레거시 딥 임포트에 대한 옵트아웃은 0.88까지만 사용 가능합니다.

TypeScript가 React Native의 기본값이 된 이유

버전 0.76 이후로 npx react-native init 명령어로 생성되는 모든 프로젝트는 TypeScript로 구성됩니다. 하지만 본질적인 변화는 네이티브 경계에서 일어났습니다. Codegen 도입 이전에 개발자들은 JavaScript에서 Objective-C나 Kotlin으로의 경계를 넘을 때 수동 타입 어설션을 작성해야 했습니다. 이는 문자열 기반의 암묵적 계약이었고, 런타임에 조용히 깨지는 문제를 일으켰습니다. Codegen은 TypeScript 사양 파일을 읽어 C++, Objective-C++, Java/Kotlin 인터페이스를 자동으로 생성합니다. TypeScript 사양이 number를 반환하는 메서드를 선언하면, 생성된 네이티브 인터페이스가 해당 제약 조건을 컴파일 시점에 강제합니다.

React Native 공식 문서에서는 기본적인 설정을 다루고 있지만, 프로덕션 환경에서 중요한 타입 안전 패턴은 그 이상입니다. 타입 안전 내비게이션 스택, 제네릭 API 훅, 상태 머신용 판별 유니온, Codegen 기반 TurboModule 사양이 핵심을 이룹니다.

Strict TypeScript API: 옵트인에서 기본값으로

Strict TypeScript API는 React Native 0.80에서 옵트인 기능으로 도입되었습니다. 0.87부터는 모든 새 프로젝트에서 필수 기본값이 되었습니다. 타입은 수동으로 관리되는 것이 아니라 React Native 소스 코드에서 직접 생성되므로, 문서화된 API와 실제 구현 간의 불일치가 해소됩니다.

tsconfig.json (0.87 이상 기본 설정)json
{
  "extends": "@react-native/typescript-config",
  "compilerOptions": {
    "strict": true,
    "exactOptionalPropertyTypes": true,
    "noUncheckedIndexedAccess": true
  }
}

이 설정을 적용하면 react-native/Libraries/Text/Text와 같은 하위 경로에서의 임포트는 타입 에러가 발생합니다. 모든 임포트는 루트 react-native 패키지에서 이루어져야 합니다. 라이브러리나 기존 코드베이스가 딥 임포트에 의존하는 경우, 0.88까지 임시 옵트아웃을 사용할 수 있습니다.

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

이 옵트아웃은 0.89에서 제거됩니다. 그 전에 마이그레이션을 완료해야 합니다.

0.87의 툴체인 요구 사항

React Native 0.87에서는 빌드 체인 전반에 걸쳐 최소 버전이 상향되었습니다.

컴포넌트최소 요구 사항
Node.js22.13.0
Kotlin2.0 (번들 버전: 2.2.0)
Android Gradle Plugin9.x
Android compileSdk34 (최소), 37 (타겟)

Android의 경우 AGP 9에 추가 설정이 필요합니다.

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

이러한 요구 사항은 0.87 이상을 대상으로 하는 모든 프로젝트에 적용됩니다. New Architecture 마이그레이션 가이드에서 이러한 변경 사항의 전체적인 맥락을 확인할 수 있습니다.

React Navigation 7을 활용한 타입 안전 내비게이션

React Navigation 7.x는 TypeScript에 대한 퍼스트 클래스 지원을 제공합니다. 핵심 패턴은 각 화면 이름과 해당 파라미터를 매핑하는 RootStackParamList 타입을 정의하고, 해당 타입을 네비게이터와 화면 컴포넌트에 전파하는 것입니다.

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;
};

화면 컴포넌트는 수동 캐스팅 없이 타입이 지정된 props를 받습니다.

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는 string — 타입에 의해 보장됨
  // route.params.source는 'feed' | 'search' — 런타임 검사 불필요
  const { articleId, source } = route.params;

  // navigation.navigate('Profile', { userId: '123' }) — 타입 체크 완료
  // navigation.navigate('Profile', {}) — 컴파일 에러: userId 누락
  return (
    <ArticleView id={articleId} referrer={source} />
  );
}

이 패턴을 통해 런타임 에러의 한 범주가 완전히 제거됩니다. 잘못된 파라미터 또는 누락된 파라미터로 화면 이동을 시도하면 앱 실행 전 빌드 시점에 실패합니다.

useNavigation 훅 타입 지정

직접적인 화면 자식 컴포넌트가 아닌 경우, useNavigation<NativeStackNavigationProp<RootStackParamList>>()를 사용하면 props 전달 없이도 동일한 타입 안전성을 확보할 수 있습니다.

Codegen을 활용한 타입 안전 TurboModule 구축

TurboModules는 기존 Native Modules 시스템을 대체합니다. TypeScript 사양 파일이 단일 정보 출처(single source of truth)로 기능하며, Codegen이 이를 기반으로 네이티브 인터페이스를 생성합니다. 사양과 네이티브 구현이 일치하지 않으면 빌드가 실패합니다.

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');

npx react-native codegen을 실행하면 대응하는 C++, Objective-C++, Java 인터페이스가 생성됩니다. 네이티브 구현은 모든 메서드 시그니처와 정확히 일치해야 합니다. 예를 들어 getStorageInfo는 3개의 숫자 필드를 가진 객체를 반환해야 하며, 다른 구조를 반환하면 네이티브 측에서 컴파일 에러가 발생합니다.

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

    // 반환 타입은 생성된 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)
    }
}

기존 아키텍처에서 문제가 되었던 ReadableMap과 NSDictionary 파싱으로 인한 암묵적 타입 변환 버그가 이 접근 방식을 통해 제거됩니다. 네이티브 모듈 시스템에 대한 자세한 내용은 네이티브 모듈 면접 질문을 참고하시기 바랍니다.

React Native 면접 준비가 되셨나요?

인터랙티브 시뮬레이터, flashcards, 기술 테스트로 연습하세요.

제네릭 타입을 활용한 타입 안전 API 훅

재사용 가능한 타입 훅 패턴을 통해 화면 간 페치 로직 중복 없이 완전한 타입 추론을 유지할 수 있습니다.

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,
  });
}

// 사용 예시 — T는 Article[]로 추론됨
interface Article {
  id: string;
  title: string;
  publishedAt: string;
}

const { data, isLoading } = useApiQuery<Article[]>(
  ['articles', 'latest'],
  '/articles?sort=latest'
);
// data.data는 Article[] — 완전한 타입 지정
// data.meta.totalPages는 number

제네릭 파라미터 T는 훅 호출 지점에서 쿼리 함수를 거쳐 결과를 소비하는 컴포넌트까지 전체 체인에 걸쳐 전파됩니다. as 캐스팅도 any 타입도 필요하지 않습니다.

판별 유니온을 활용한 상태 머신 설계

화면의 복잡한 상태(로딩, 에러, 빈 상태, 로드 완료)는 선택적 필드의 집합이 아닌 판별 유니온으로 모델링하는 것이 최적입니다.

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는 여기서 string — TypeScript가 자동으로 좁혀줌
      return <ErrorBanner message={state.error} retries={state.retryCount} />;
    case 'empty':
      return <EmptyState message={state.message} />;
    case 'loaded':
      // state.data는 T — 완전한 타입 지정
      return renderItem(state.data);
  }
}

이 패턴을 통해 불가능한 상태를 타입 수준에서 표현 불가능하게 만들 수 있습니다. loading 상태가 오래된 data를 잘못 보유할 수 없으며, error 상태에는 항상 디버깅을 위한 컨텍스트가 포함됩니다.

부분적 상태 객체의 함정

흔한 안티패턴: { isLoading: boolean; error?: string; data?: T }. 이 정의에서는 { isLoading: true, error: 'fail', data: [...] }와 같은 상태가 허용됩니다. 세 가지 모순된 신호가 동시에 존재하게 됩니다. 판별 유니온을 사용하면 이러한 상태를 타입 수준에서 방지할 수 있습니다.

React Native TypeScript 면접 질문

New Architecture와 TypeScript가 표준이 된 2026년, 시니어 모바일 엔지니어링 팀 면접에서 실제로 출제되는 질문들을 다룹니다.

Codegen은 JavaScript-네이티브 경계에서 어떻게 타입 안전성을 보장합니까?

Codegen은 TypeScript(또는 Flow) 사양 파일을 읽어 C++, Objective-C++, Java/Kotlin 인터페이스 코드를 생성합니다. 생성된 네이티브 인터페이스는 사양에 정의된 메서드 시그니처, 파라미터 타입, 반환 타입을 정확하게 강제합니다. 네이티브 구현이 일치하지 않는 경우(사양이 Double을 선언했는데 Int를 반환하거나 구조체에서 필드를 누락하는 등) 네이티브 컴파일러가 빌드를 거부합니다. 이를 통해 타입 에러가 런타임 크래시에서 빌드 시점의 실패로 이동합니다.

Strict TypeScript API란 무엇이며 왜 중요합니까?

Strict TypeScript API는 수동으로 작성된 .d.ts 파일 대신 React Native의 소스 코드에서 직접 타입을 생성합니다. 0.87에서 필수 기본값이 되었습니다. 임포트를 루트 react-native 패키지로 제한하고 딥 임포트를 지원 중단합니다. 이를 통해 안정적인 공개 API 표면이 정의되며, 사용자가 strict 타입만 사용하는 한 내부 리팩토링으로 인해 사용자 코드가 깨지지 않습니다. customConditions를 통한 임시 옵트아웃은 0.88까지 사용 가능합니다.

중첩된 네비게이터 간에 React Navigation 파라미터를 어떻게 타입 지정합니까?

네비게이터별로 ParamList 타입을 정의하고 NavigatorScreenParams를 사용하여 조합합니다. 스택 내에 중첩된 탭 네비게이터의 경우, 스택의 파라미터 리스트가 탭의 파라미터 리스트를 참조합니다: type RootStack = { Main: NavigatorScreenParams<TabParamList>; Modal: { id: string } }. 모든 navigate() 호출이 전체 중첩 계층 구조를 통해 타입 체크되어 잘못된 화면 이름이나 누락된 파라미터를 컴파일 시점에 감지합니다.

판별 유니온은 React Native 상태 관리에서 어떤 문제를 해결합니까?

판별 유니온은 상호 배타적인 상태(loading, error, loaded)를 status 필드를 키로 하는 유니온 타입의 별도 분기로 모델링합니다. TypeScript는 switch 문의 각 분기에서 타입을 자동으로 좁혀주므로, state.data에 대한 접근은 state.status === 'loaded'인 경우에만 가능합니다. 이를 통해 로딩 인디케이터가 에러 데이터와 동시에 표시되는 버그를 방지할 수 있습니다. 선택적 필드와 boolean 플래그로는 이 범주의 버그를 방지할 수 없습니다.

TurboModules와 기존 Native Modules 시스템의 차이점을 설명해 주십시오.

Native Modules는 비동기 브리지를 통해 통신하며 모든 데이터를 JSON으로 직렬화했습니다. TurboModules는 JSI(JavaScript Interface)를 사용하여 동기적이고 직접적인 C++ 호출을 수행하며 직렬화 오버헤드가 없습니다. 또한 지연 로딩(앱 시작 시가 아닌 첫 사용 시 로드)을 통해 콜드 스타트 시간을 줄이고, Codegen을 사용하여 TypeScript 사양에서 타입 안전 인터페이스를 생성합니다. 기존 시스템은 ReadableMap / NSDictionary 파싱에 의한 런타임 타입 변환에 의존했지만, TurboModules는 컴파일 시점에 타입을 강제합니다.

React Native 0.87에서 제거된 API는 무엇입니까?

0.87에서는 InteractionManager(requestIdleCallback 사용 권장), NativeMethods / NativeMethodsMixin 타입(HostInstance 사용 권장), 일부 StatusBar props가 제거되었습니다. src/private/ 경로로의 딥 임포트는 타입 에러가 발생합니다. TurboModules가 항상 활성화되어 있기 때문에 useTurboModules 플래그도 제거되었습니다.

React Native 면접 질문 전체 가이드에서는 아키텍처, 성능, 디버깅 주제를 다루고 있습니다.

연습을 시작하세요!

면접 시뮬레이터와 기술 테스트로 지식을 테스트하세요.

Sources

React Native TypeScript 2026의 핵심 사항

  • Strict TypeScript API(0.87에서 필수화)는 안정적인 공개 표면으로 임포트를 제한하여 내부 변경으로 인한 파손을 방지합니다. 레거시 딥 임포트에 대한 옵트아웃은 0.88까지만 사용 가능합니다
  • Codegen은 TypeScript 사양 파일에서 네이티브 인터페이스를 생성하여 JS-네이티브 경계의 타입 에러를 런타임 크래시에서 빌드 시점 실패로 전환합니다
  • 0.87의 툴체인 최소 요구 사항: Node.js 22.13 이상, Kotlin 2.0 이상, AGP 9, compileSdk 34 이상
  • RootStackParamList와 NativeStackScreenProps를 사용한 타입 안전 내비게이션 파라미터로 잘못된 화면 이름과 누락된 파라미터를 앱 실행 전에 감지할 수 있습니다
  • 판별 유니온은 화면 상태를 상호 배타적인 분기로 모델링하여 불가능한 상태를 타입 수준에서 표현 불가능하게 만듭니다
  • 타입 사양을 갖춘 TurboModules는 기존 ReadableMap / NSDictionary 파싱을 대체하여 JavaScript에서 C++, 플랫폼 네이티브 코드까지 완전한 타입 안전성을 구현합니다
  • TanStack Query를 사용한 제네릭 API 훅은 엔드포인트에서 컴포넌트까지 수동 캐스팅 없이 타입 추론을 유지합니다

연습을 시작하세요!

면접 시뮬레이터와 기술 테스트로 지식을 테스트하세요.

오늘의 챌린지

React Native 코드의 버그를 찾을 수 있나요

실제 코드 한 조각, 숨은 버그 하나, 하루 한 번. 계정 없이 바로 도전할 수 있습니다.

Anthony Fillion-Maillet

작성자

Anthony Fillion-Maillet

SharpSkill 창업자

10년 이상 풀스택 개발을 해왔습니다. SharpSkill을 운영하며 이곳에 게시되는 모든 내용에 책임을 집니다.

2026년 8월 25일 업데이트

태그

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

공유

관련 기사