개발에 AtoZ까지

React Native 커스텀 네이티브 모듈, New Architecture에선 어떻게 바뀌나 본문

프론트엔드

React Native 커스텀 네이티브 모듈, New Architecture에선 어떻게 바뀌나

AtoZ 개발자 2026. 7. 16. 20:58
반응형

😀 개요

안녕하세요 😀

지난 「React Native New Architecture 마이그레이션, 어디서부터 확인해야 할까」 글(발행 후 URL 연결) 마지막에, 다음 편에서는 커스텀 네이티브 모듈을 Codegen 기반으로 바꾸는 과정을 다루겠다고 했습니다. 이번 글이 그 편입니다.

New Architecture로 넘어가다 보면 이런 상황을 자주 만납니다.

  • 잘 쓰던 자체 네이티브 모듈이 New Architecture에서 인식되지 않거나 경고가 뜬다.
  • 빌드 중 Codegen, spec, TurboModule 같은 처음 보는 단어가 에러에 등장한다.
  • "내 모듈도 전부 다시 만들어야 하나?" 싶어 손이 멈춘다.

저도 문서를 처음 봤을 때는 TurboModule, Codegen, JSI가 다 따로 노는 개념처럼 보였습니다. 그런데 하나만 잡으면 나머지가 따라옵니다. 바로 "JavaScript와 네이티브가 주고받는 값의 타입을 미리 못 박는다"는 것입니다.

🎯 이 글을 끝까지 읽으면

  • 기존 NativeModule과 TurboModule이 정확히 무엇이 다른지 한 문장으로 말할 수 있습니다.
  • 내 모듈을 지금 전환할지, Interop에 맡길지 판단하는 기준이 생깁니다.
  • 복사해서 바로 쓸 수 있는 최소 spec 파일과 codegen 설정을 가져갑니다.

즉, "일단 다 TurboModule로 바꾼다"가 아니라, 무엇을 왜 바꾸는지 알고 필요한 것만 바꾸는 방법이 목표입니다.

🧭 먼저, 이 글의 근거 범위

이 글은 제가 특정 상용 모듈을 직접 전환해 본 경험담이 아니라, React Native 공식 New Architecture 문서를 기준으로 정리한 개념·결정 가이드입니다. 아래 spec·설정 예시는 문서 기준으로 재현 가능하게 적었지만, RN 버전과 플랫폼 설정에 따라 세부가 다를 수 있습니다.

그래서 네이티브 구현 세부와 실제 빌드 결과는 반드시 본인 프로젝트에서 확인하시고, [직접 확인] 표시가 있는 자리는 여러분 환경의 결과로 채우시길 권합니다. 로그를 캡처해 올릴 때는 서명 키·API 키·사내 경로가 화면에 남지 않게 가려 주세요.

🧱 기존 NativeModule은 왜 New Architecture에서 걸릴까?

예전 방식(NativeModule)은 JavaScript와 네이티브가 Bridge를 통해 비동기로 메시지를 주고받는 구조였습니다. 이때 주고받는 값의 타입은 런타임에야 확인됐습니다. 숫자를 기대했는데 문자열이 와도 실행하기 전까지는 모릅니다.

TurboModule은 이 지점을 바꿉니다. 쉽게 말하면 JavaScript와 네이티브 사이에 "타입 계약서"를 미리 만들어 두고, 그 계약대로만 호출하게 합니다. 이 계약서를 자동으로 만들어 주는 도구가 Codegen입니다.

그림 1. 기존 NativeModule은 Bridge를 거쳐 런타임에 타입이 정해지고, TurboModule은 Codegen이 만든 타입 계약(spec)을 통해 JSI로 더 직접 호출됩니다.

그래서 "TurboModule로 바꾼다"는 말은 대부분 "내 모듈에 타입 명세(spec)를 붙이고, 그 명세대로 네이티브를 구현한다"는 뜻입니다. 마법이 아니라 규칙이 하나 늘어난 것에 가깝습니다.

🔤 Codegen이 요구하는 것 — spec 파일

Codegen은 정해진 위치·이름 규칙의 spec 파일을 읽어 네이티브 인터페이스를 생성합니다. 최소 형태의 spec은 이렇게 생겼습니다. 파일 이름은 Native로 시작해야 하고, 인터페이스 이름은 Spec으로 내보냅니다.

// specs/NativeCalculator.ts
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';

export interface Spec extends TurboModule {
  multiply(a: number, b: number): number;
}

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

그리고 package.json에 Codegen이 어디를 읽을지 알려 줍니다.

{
  "codegenConfig": {
    "name": "AppSpec",
    "type": "modules",
    "jsSrcsDir": "specs",
    "android": {
      "javaPackageName": "com.myapp.spec"
    }
  }
}

여기까지가 JavaScript 쪽 준비입니다. 이 설정이 있으면 iOS는 pod install 단계에서, Android는 빌드 단계에서 Codegen이 돌며 네이티브가 구현해야 할 인터페이스를 만들어 줍니다.

한 가지 주의: spec에서 쓸 수 있는 타입은 제한적입니다(숫자·문자열·불리언·객체·배열·Promise·콜백 등). 임의의 유니온이나 복잡한 타입은 안 될 수 있으니, 지원 타입은 본인 RN 버전의 공식 문서에서 확인하세요.

⚙️ 네이티브 구현은 어디가 달라질까?

네이티브 쪽은 Codegen이 만들어 준 인터페이스를 구현하는 형태로 바뀝니다. 예전에는 자유롭게 메서드를 노출했다면, 이제는 생성된 spec(예: NativeCalculatorSpec)을 상속·구현해야 합니다.

전체 네이티브 코드는 플랫폼별로 길고 버전에 따라 달라져서, 이 글에서는 형태만 짚겠습니다. 실제 구현은 공식 "Turbo Native Modules" 가이드를 그대로 따라가는 것을 권장합니다(아래 참고 자료). 핵심만 정리하면 이렇습니다.

  • 생성된 spec 인터페이스(예: NativeCalculatorSpec)를 구현한다.
  • 모듈 이름은 spec에서 지정한 이름(NativeCalculator)과 정확히 일치시킨다.
  • 패키지/모듈 등록부에 이 TurboModule을 연결한다.

🧩 그럼 내 모듈, 지금 다 바꿔야 할까?

여기서 오해를 풀고 갑니다. New Architecture라고 해서 기존 NativeModule이 전부 즉시 죽는 것은 아닙니다. 지난 글에서 다룬 Interop Layer(상호운용 계층)가 예전 방식 모듈도 상당수 그대로 굴려 줍니다.

그래서 판단 순서는 이렇게 잡는 편이 안전합니다.

그림 3. 새로 만드는 모듈, 자주 고치는 핵심 모듈, 거의 안 건드리는 모듈에 따라 전환 여부를 다르게 판단합니다.

  • 새로 만드는 모듈이라면 처음부터 TurboModule로 만드는 편이 낫습니다. 어차피 앞으로의 기준이니까요.
  • 자주 고치고 성능에 민감한 핵심 모듈이면 전환 우선순위를 높입니다.
  • 거의 안 건드리고 Interop으로 잘 도는 모듈이면 굳이 서두르지 않아도 됩니다.

🛠️ 자주 막히는 지점

공식 문서·이슈에서 반복적으로 나오는 대표적인 막힘입니다. 

  • spec을 못 찾음: 파일 이름이 Native로 시작하는지, codegenConfig.jsSrcsDir 경로가 맞는지 확인합니다.
  • 이름 불일치: JS의 getEnforcing('NativeCalculator') 이름과 네이티브 모듈 이름이 정확히 같아야 합니다.
  • 타입 관련 빌드 실패: spec에 지원되지 않는 타입을 썼을 때가 많습니다. 타입을 문서 기준으로 단순화합니다.
  • iOS에서 반영 안 됨: pod install을 다시 돌려 Codegen을 재생성합니다. 그래도 안 되면 캐시를 정리합니다.
# iOS: Codegen 재생성을 위해 Pods 재설치
cd ios
rm -rf Pods Podfile.lock build
bundle exec pod install
cd ..

# Metro 캐시 초기화
npx react-native start --reset-cache

✅ 전환 체크리스트

그대로 복사해 프로젝트 이슈에 붙여 쓰셔도 됩니다.

[판단]
- [ ] 이 모듈은 새로 만드나 / 자주 고치나 / 거의 안 건드리나
- [ ] Interop으로 지금 잘 도는지 먼저 확인

[spec]
- [ ] specs/Native○○.ts 생성 (파일명 Native 시작, interface Spec)
- [ ] package.json codegenConfig (name/type/jsSrcsDir/android.javaPackageName)
- [ ] spec 타입을 지원 범위 안으로 단순화

[네이티브]
- [ ] Codegen 생성 인터페이스 구현 (공식 Turbo Native Modules 가이드)
- [ ] 모듈 이름 = JS getEnforcing 이름 일치
- [ ] 패키지 등록

[빌드/검증]
- [ ] iOS pod install로 Codegen 재생성 확인
- [ ] Android 빌드 확인
- [ ] 실제 호출 결과 확인 후 기록

🤔 정리하며

핵심은 하나입니다. TurboModule 전환은 "새 기술을 배우는 일"이라기보다 "JavaScript와 네이티브가 주고받는 값의 타입을 spec으로 못 박는 일"입니다. 이 관점만 잡으면 Codegen도, 빌드 에러도 결국 같은 곳을 가리킵니다.

그리고 모든 모듈을 한 번에 바꿀 필요는 없습니다. 새로 만들 때는 TurboModule로, 기존 것은 Interop으로 버티다가 필요할 때 전환하는 것이 현실적입니다.

🫡 마무리

이번 글은 커스텀 네이티브 모듈이 New Architecture에서 어떻게 바뀌는지, 그리고 전환을 어떻게 판단하는지에 집중했습니다. 마이그레이션 전체 순서와 라이브러리 호환 확인이 궁금하다면, 앞 글 「React Native New Architecture 마이그레이션, 어디서부터 확인해야 할까」를 함께 보시면 흐름이 이어집니다.

다음 글에서는 커스텀 (화면에 그려지는 네이티브 컴포넌트)를 Fabric에서 만드는 과정을, 역시 최소 예제로 정리해 보겠습니다.

읽어 주셔서 감사합니다 🙇


참고 자료

반응형
Comments