React Native Codegen: 타입 계약을 왜 플랫폼 코드로 생성하는가
JavaScript 명세에서 C++·Android·iOS 연결 인터페이스를 다시 만들고, 숫자 타입 변경을 반영하지 않은 플랫폼 구현을 실행 전에 발견하는 검증 흐름을 세운다.
목차
표준·구현·측정·해석 표시는 무엇인가요?
- 표준웹 표준이나 언어 명세가 정한 동작
- 구현특정 기술이나 브라우저가 실제로 구현한 동작
- 측정명시한 환경에서 직접 실행해 관찰한 결과
- 해석앞선 근거에서 도출한 설계 판단
- 미확인아직 공식 근거나 재현 결과를 확인하지 못한 내용
30초 요약
React Native Codegen은 TypeScript·Flow Spec을 찾아 다음 경계를 생성한다.
한 JavaScript 타입 명세
→ C++ 연결 코드
→ Android Java 인터페이스
→ iOS Objective-C++ 인터페이스와 연결 코드Spec을 바꾸면 생성 파일을 직접 고치지 않는다. Codegen을 다시 실행하고 Android·iOS 실제 구현을 새 인터페이스에 맞춘 뒤 양쪽 플랫폼을 모두 컴파일한다. 컴파일 성공은 타입 경계의 증거이며, 저장 값의 업무 의미는 별도 테스트로 확인한다.
이 글을 관통하는 상황: 금액을 number로 바꾸자 생성 계약과 구현이 어긋난다
6편의 영수증 저장 API는 영수증 전체를 payload: string으로 보냈다. 그때는 Android만 지원하고
iOS에는 구현이 없었다. 이 글의 시작 fixture에서는 다음 제품 단계로 넘어가 iOS 지원도 추가됐고,
양쪽 모두 아직 saveDraft(id, payload)라는 두 문자열 구현을 등록한 상태라고 가정한다.
이제 금액을 문자열 내부에 숨기지 않고 숫자 매개변수로 드러내기로 했다. Spec은
Specification(명세)의 줄임말이며, 다음 파일이 이 모듈의 원본 타입 계약이다.
// specs/NativeReceiptStore.ts
import type { TurboModule } from 'react-native'
import { TurboModuleRegistry } from 'react-native'
export interface Spec extends TurboModule {
saveDraft(
id: string,
merchant: string,
total: number,
): Promise<boolean>
loadDraft(id: string): Promise<string | null>
}
export default TurboModuleRegistry.get<Spec>('NativeReceiptStore')이전 Android·iOS 구현은 saveDraft(id, payload)처럼 문자열 두 개를 받는다. JavaScript 호출부만
saveDraft('receipt-42', '동네 상점', 12900)로 바꾸고 양쪽 구현은 그대로 둔 상태라고 하자.
fixture, 즉 미리 준비한 가상 빌드 증거는 Codegen을 다시 실행한 뒤 다음 차이를 기록한다. 실제 컴파일러의 고정 오류 문구가 아니라 생성 계약과 기존 구현의 핵심 차이만 정리한 기록이다.
기록을 보기 전에 예측한다.
- TypeScript의
number는 Android와 iOS 인터페이스에서 각각 어떤 타입이 되는가? - 매개변수가 두 개에서 세 개로 바뀌었는데 기존 네이티브 메서드가 새 인터페이스를 구현할 수 있는가?
- Android만 수정해 빌드하면 iOS 구현까지 맞았다는 증거가 되는가?
아래 표의 iOS protocol은 구현 클래스가 따라야 하는 생성된 함수 목록과 계약이다.
Spec saveDraft(id: string, merchant: string, total: number)
생성 Android 경계 saveDraft(String id, String merchant, double total, Promise result)
기존 Android 구현 saveDraft(String id, String payload, Promise result)
Android compile 생성 인터페이스 메서드를 구현하지 못함
생성 iOS 경계 saveDraft(id: NSString, merchant: NSString, total: double, resolve/reject)
기존 iOS 구현 saveDraft(id: NSString, payload: NSString, resolve/reject)
iOS build diagnostic 새 protocol 메서드 미구현 진단, 경고·오류 여부는 빌드 설정에 따름Promise result와 resolve/reject는 나중에 완료 또는 실패를 전달하는 각 플랫폼의 연결 인자다.
double은 TypeScript의 필수 number를 받는 Android·iOS 숫자 경계 타입이다. iOS의
NSNumber *는 값이 없을 수도 있는 nullable·optional 숫자 매개변수에 사용된다. 핵심은
TypeScript 호출이 맞아도 네이티브 구현이 새 생성 인터페이스를 따르지 않으면 Android 컴파일
오류나 iOS protocol 준수 진단처럼 계약 차이가 빌드 중 드러날 수 있다는 점이다.
Codegen은 명세를 플랫폼 경계로 번역한다
이 연결 덕분에 Spec과 플랫폼 구현의 불일치가 실제 사용자 실행까지 숨어 있지 않고 생성·컴파일 단계에서 드러날 수 있다.
생성 파일이 아니라 Spec과 구현을 고친다
따라서 생성된 NativeReceiptStoreSpec.java의 double을 다시 String으로 직접 바꾸는 교정은
사용하지 않는다. 다음 빌드가 파일을 다시 만들 수 있고, JavaScript 명세와 네이티브 구현의 차이를
숨기기 때문이다.
수정할 원본은 두 종류다.
- 제품 API를 선언하는
specs/NativeReceiptStore.ts - 생성된 인터페이스를 실제로 구현하는 Android·iOS 소스
금액을 숫자로 정한 결정이 맞다면 Spec은 유지하고, 양쪽 구현이 merchant와 숫자 total을 받도록
수정한다. 결정 자체가 잘못됐다면 Spec부터 되돌리고 다시 생성한다.
한 Spec 변경을 양쪽 빌드로 검증한다
이 글은 실제 저장소 저장 코드를 모두 제공하는 네이티브 실습 프로젝트가 아니라, 미리 준비한
ReceiptFixture의 생성·빌드 증거를 따라가는 가상 검증 절차다. 그래도 각 행동과 확인할 결과는
다음처럼 1:1로 닫는다. fixture는 앱 루트에 android가 있고, iOS 의존성 도구인 CocoaPods가 만든
프로젝트 묶음(workspace) ios/ReceiptFixture.xcworkspace와 빌드할 앱 설정 이름인 Xcode scheme
ReceiptFixture가 있다고 가정한다.
- 위 Spec과 JavaScript 호출부를 세 매개변수 형태로 바꾼다.
- 앱 루트에서
cd android && ./gradlew generateCodegenArtifactsFromSchema를 실행한다. - 앱 루트로 돌아와
rg "saveDraft" android/app/build/generated/source/codegen으로 생성된String,String,double,Promise경계를 확인한다. cd android && ./gradlew assembleDebug를 실행해 기존 두-문자열 구현이 새 메서드를 구현하지 못한다는 실패를 확인한다.- Android 구현을 아래 함수 모양에 맞춘 뒤 같은
assembleDebug를 다시 실행한다.
@Override
public void saveDraft(
String id,
String merchant,
double total,
Promise promise
) {
// fixture의 Android 저장 구현
}- 앱 루트에서 다음 명령으로 iOS Codegen과 simulator용 컴파일을 실행한다.
xcodebuild \
-workspace ios/ReceiptFixture.xcworkspace \
-scheme ReceiptFixture \
-configuration Debug \
-sdk iphonesimulator \
build- 첫 iOS 빌드에서 기존 두-문자열 구현이 새 protocol 메서드를 구현하지 않았다는 진단을 확인한다. 이 진단이 경고인지 오류인지는 프로젝트 빌드 설정에 따라 달라질 수 있으므로 빌드 성공만으로 무시하지 않는다. 구현을 다음 함수 모양에 맞춘 뒤 같은 명령을 다시 실행해 해당 진단이 사라졌는지 확인한다.
- (void)saveDraft:(NSString *)identifier
merchant:(NSString *)merchant
total:(double)total
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject {
// fixture의 iOS 저장 구현
}- fixture의 두 플랫폼 구현은
merchant문자열과total숫자를 JSON 객체로 직렬화해 저장하고,loadDraft는 그 JSON 문자열을 그대로 돌려준다고 계약한다. 양쪽 앱에서 다음 완결 컴포넌트의 버튼을 눌러 저장·재조회 결과를 화면에 남긴다.JSON.parse의 결과는 외부 값이므로unknown에서 숫자 필드를 확인한다. 매 실행마다 새 ID를 사용해 이전 저장값이 현재 실패를 가리지 않게 한다.
import { useState } from 'react'
import { Pressable, Text, View } from 'react-native'
import NativeReceiptStore from './specs/NativeReceiptStore'
export default function VerifyReceiptTotal() {
const [status, setStatus] = useState('검증 대기')
async function verifyStoredTotal() {
if (!NativeReceiptStore) {
setStatus('모듈 미지원')
return
}
const id = `receipt-codegen-${Date.now()}`
try {
const saved = await NativeReceiptStore.saveDraft(id, '동네 상점', 12900)
if (!saved) {
setStatus('새 저장 실패')
return
}
const raw = await NativeReceiptStore.loadDraft(id)
const parsed: unknown = raw === null ? null : JSON.parse(raw)
const total =
typeof parsed === 'object' && parsed !== null && 'total' in parsed
? parsed.total
: undefined
const isStoredNumber = typeof total === 'number' && total === 12900
setStatus(isStoredNumber ? '저장 금액 확인: 12900' : '저장 금액 불일치')
} catch {
setStatus('저장 검증 오류')
}
}
return (
<View style={{ padding: 24 }}>
<Pressable onPress={verifyStoredTotal}>
<Text>금액 저장 검증</Text>
</Pressable>
<Text>{status}</Text>
</View>
)
}기대 결과는 양쪽 화면에 저장 금액 확인: 12900이 표시되는 것이다. 생성 코드 검색, 실패한 첫
컴파일, 수정 후 성공한 컴파일, 실행 화면을 서로 다른 증거로 남긴다.
교정 후 fixture의 증거는 다음처럼 닫힌다.
Android generated contract total: double
Android implementation generated method 구현 완료
Android compile 성공
Android stored total 12900
iOS generated contract total: double
iOS implementation generated method 구현 완료
iOS build 성공, protocol 준수 진단 없음
iOS stored total 12900시뮬레이터와 에뮬레이터에서 생성·컴파일·저장 결과를 검증할 수 있으므로 이 타입 계약 확인에 실기기가 필수는 아니다. 실제 기기 전용 API가 들어간 모듈이라면 그 기능의 하드웨어 검증은 해당 기능 글에서 별도로 추가한다.
생성된 타입은 업무 의미를 대신하지 않는다
예를 들어 id와 merchant가 모두 string이면 호출 순서를 바꿔도 타입만으로는 발견하지 못할
수 있다. total: number도 음수 금액을 허용해야 하는지까지 말하지 않는다. 그래서 플랫폼
컴파일 뒤에도 저장·재조회 통합 테스트와 업무 입력 검증이 필요하다.
같은 상황을 다시 진단한다
문제 Spec의 total을 number로 바꾼 뒤 Android 컴파일 오류와 iOS protocol 진단 발생
잘못된 판단 생성 Java·Objective-C++ 파일을 직접 고치면 계약 변경이 끝남
관찰 생성 인터페이스는 세 인자와 숫자 total을 요구하지만 기존 구현은 두 문자열을 유지
원인 원본 Spec 변경에 실제 플랫폼 구현이 아직 맞춰지지 않음
행동 생성 파일이 아니라 Android·iOS 구현을 새 인터페이스에 맞춤
검증 양쪽 생성·빌드 진단 후 typeof total === 'number'와 값 12900을 재조회로 확인풀 리퀘스트에서는 다음을 확인한다.
- Spec이 제품 API 결정의 원본으로 명확한가?
- 생성 파일을 직접 수정하지 않고 다시 만들 수 있는 상태인가?
- Spec 변경 뒤 Android와 iOS 생성·컴파일을 모두 실행했는가?
- 생성된 플랫폼 타입을 실제 구현이 정확히 따르는가?
- 컴파일 성공과 저장 값의 업무 의미 검증을 구분했는가?
- 사용 중인 React Native 버전의 Codegen으로 결과를 만들었는가?
기억할 문장은 짧다.
Codegen은 하나의 Spec을 양쪽 플랫폼의 컴파일 가능한 경계로 반복해서 번역한다.
다음 글에서는 React 컴포넌트의 생명주기와 운영체제 앱 생명주기가 왜 다른지 살펴본다.
Active recall
기억에서 꺼내 보기
답을 쓰지 않아도 됩니다. 먼저 머릿속으로 답한 뒤 펼쳐서 정답·이유·흔한 오해를 비교하세요.
01Codegen은 단지 반복 타이핑을 줄이는 편의 기능이라서 꺼도 타입 계약에는 차이가 없을까?
정답
아니다. 수동 작성도 이론상 가능하지만, Codegen은 한 Spec에서 C++와 양쪽 플랫폼 인터페이스를 다시 만들어 구현이 같은 경계를 따르게 한다.
관련 설명 다시 읽기왜 그런가
생성된 인터페이스를 구현하는 과정에서 JavaScript 명세와 네이티브 메서드 모양의 차이가 플랫폼 빌드 오류로 드러날 수 있다.
선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.
02Spec을 바꾼 뒤 Android 생성 파일만 직접 고치면 영구적인 해결이 될까?
정답
아니다. 생성 파일은 Spec에서 다시 만드는 결과이며 다음 Codegen 실행이 덮어쓸 수 있다. Spec과 실제 플랫폼 구현을 수정해야 한다.
관련 설명 다시 읽기왜 그런가
원본과 생성 결과의 소유권을 거꾸로 두면 로컬 우연과 재생성 후 상태가 달라진다.
선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.
03total을 string에서 number로 바꿨는데 Android 구현이 문자열 매개변수를 유지하면 Codegen 뒤 무엇을 기대해야 할까?
정답
생성된 Android 인터페이스는 number를 double로 요구하므로 기존 구현이 그 메서드를 올바르게 구현하지 못한다는 컴파일 오류를 기대한다.
관련 설명 다시 읽기왜 그런가
같은 필수 number 매개변수는 iOS에서도 double 경계가 되며, 기존 구현은 새 protocol을 따르지 않는다는 진단이 생길 수 있어 양쪽을 각각 맞춰야 한다.
선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.
04Android 빌드가 통과하면 같은 Codegen Spec을 사용하는 iOS 구현도 맞다고 확정할 수 있을까?
정답
아니다. Codegen은 플랫폼별 인터페이스를 만들고 실제 구현도 각각 존재하므로 Android와 iOS를 모두 생성·컴파일해야 한다.
관련 설명 다시 읽기왜 그런가
공통 명세는 두 플랫폼 검증을 하나로 합치는 것이 아니라 동일한 기준을 제공한다.
선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.
05두 string 매개변수의 순서를 잘못 넘겨도 Codegen이 업무 의미까지 알아서 막을까?
정답
아니다. 생성 경계는 지원 타입과 메서드 모양을 맞추지만 같은 타입 안의 업무 의미와 저장 결과는 테스트로 검증해야 한다.
관련 설명 다시 읽기왜 그런가
타입 생성과 런타임 데이터·업무 규칙 검증은 서로 보완하는 다른 증거다.
선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.
출처와 검증 범위
아래 날짜는 링크를 마지막으로 열어 본 날입니다. 문서 상단의 검증일은 글의 설명과 적용 범위를 다시 확인한 날입니다.
- What is Codegen?React Native · 공식 문서 · 확인 2026-08-09
- Using CodegenReact Native · 공식 문서 · 확인 2026-08-09
- The Codegen CLIReact Native · 공식 문서 · 확인 2026-08-09
- AppendixReact Native · 공식 문서 · 확인 2026-08-09
- Native ModulesReact Native · 공식 문서 · 확인 2026-08-09
- React Native 0.86 Objective-C++ module method serializerReact Native · 소스 코드 · 확인 2026-08-09