안녕하세요!!!! 😆 😆 지난 포스팅에서 React Native New Architecture에 대한 대략적인 개요에 대해 알아보았는데요~ 제가 너무 큰 틀만 써서, 잘 이해가 되지 않았을 수도 있습니다. 이번 포스팅 부터는 새로운 아키텍처에서 등장한 모든 구성 요소를 함께 살펴볼 예정입니다. 오늘은 그 중에서도 첫번째 구성 요소인 Codegen
에 대해 자세히 파헤쳐 볼 예정입니다! +_+
그럼 바로 시작해봅시다~!
Codegen
은 정적 타입 검사기이자, JavaScript 코드를 Native 코드로 자동으로 생성해주는 네이티브 코드 생성기입니다. (말 그대로 Code generator! 🤩)
새로운 아키텍처에서는 구 아키텍처의 브릿지의 역할을 JSI
라는 것이 대신합니다. JSI
는 정적 타입 언어인 C++로 작성되어있어 컴파일할 때 타입이 명확해야하지만, JavaScript는 동적 타입 언어이므로 런타임 때 타입이 결정됩니다. 서로 타입을 해석하는 시점이 다르기 때문에, JavaScript와 C++ 간에는 타입 불일치, 잘못된 값 전달 등의 통신 문제가 발생할 수 있습니다.
Codegen
은 이러한 문제를 방지하기 위해 도입되었습니다.
Codegen
은 앱 빌드 시에 JavaScript와 Native 모듈 통신을 위한 C++ 중간 인터페이스 코드(glue code)를 자동으로 생성합니다. C++로 생성하기 위해 타입을 빌드 타임에 미리 정적으로 검사해, 실행 중 발생할 수 있는 오류를 사전에 방지합니다.
프로젝트 내부에는 모듈이 정의된 파일들이 존재하는데요, Codegen
은 앱을 빌드할 때 이들 중에서 TurboModule
을 확장한 파일만을 찾습니다. 이러한 파일들은 Codegen
을 대상으로한 모듈 정의 파일입니다.
Codegen 대상 정의 파일 예시:
// NativeCalculator.ts
import { TurboModuleRegistry }, type { TurboModule } from 'react-native';
// 타입 정의
export interface Spec extends TurboModule {
add(a: number, b: number): number;
}
// Calculator의 이름을 가진 모듈 등록
export default TurboModuleRegistry.getEnforcing<Spec>('Calculator');
위 정의 파일의 타입을 다음과 같은 표로 정의할 수 있습니다.
항목 | 값 |
---|---|
함수 이름 | add |
인자 목록 | a: number , b: number |
반환 타입 | number |
👶🏻: 만약 정의 파일이 타입이 없는
.js
파일이라면...?
😎: JavaScript는 타입 정보를 명시하지 않기 때문에, 단순한.js
파일만 사용할 경우Codegen
은 정확한 타입을 추론할 수 없습니다.
그 결과, C++ 인터페이스를 제대로 생성하지 못해 빌드를 실패하거나, 해당 파일이 Codegen 대상에서 아예 무시될 수 있습니다.
=> 모듈 정의 파일은Native<ModuleName>.ts
또는.js
+Flow
타입 사용이 권장됩니다.
👶🏻: 만약 정의 파일에 문법 혹은 타입 오류가 있다면...?
😎:Codegen
실행 중에 바로 에러를 발생시켜 빌드를 실패하게 됩니다.
Codegen
이 모듈 정의 파일에 문제가 없음을 확인하면, JavaScript와 Native 간 통신을 중계할 C++ 중간 인터페이스(Glue Code)를 생성합니다.
예를 들어, 위 정의 파일에 있던 add 함수는 다음과 같은 형태로 변환됩니다:
// NativeCalculatorSpec.h
#pragma once
#include <jsi/jsi.h>
#include <ReactCommon/TurboModule.h>
namespace facebook::react {
class JSI_EXPORT NativeCalculatorSpec : public TurboModule {
public:
NativeCalculatorSpec(std::shared_ptr<CallInvoker> jsInvoker)
: TurboModule("Calculator", jsInvoker) {}
virtual double add(double a, double b) = 0;
};
} // namespace facebook::react
Codegen
은 새로운 아키텍처의 두 가지 핵심 구성 요소인 TurboModule
과 Fabric
을 위한 Native 코드를 자동으로 생성합니다. 이 두 구성 요소는 모두 Codegen
이 생성한 Native 코드 없이는 제대로 동작할 수 없습니다. Codegen
은 네이티브 구현부의 로직을 대신 짜주진 않지만, 그 로직을 작성할 수 있도록 하는 틀(인터페이스, 바인딩 구조 등)을 자동으로 설계해줍니다.
Codegen
을 사용하는 이점은 네이티브 모듈과 구성 요소를 만들고 유지 관리하는 데 필요한 작업량을 줄여준다는 것입니다. 🤩
// CalculatorModule.java
package com.yourapp;
import com.facebook.react.turbomodule.core.interfaces.TurboModule;
import com.facebook.react.bridge.ReactApplicationContext;
public class CalculatorModule extends NativeCalculatorSpec {
public CalculatorModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public double add(double a, double b) {
// TODO 개발자가 직접 작성
}
}
// Calculator.mm
#import "NativeCalculatorSpec.h"
using namespace facebook;
@interface Calculator : NSObject <NativeCalculatorSpec>
@end
@implementation Calculator
- (double)add:(double)a b:(double)b {
// TODO 개발자가 직접 작성
}
@end
이렇게 생성된 네이티브 코드의 틀은 이제 브릿지를 거치지 않고도 JavaScript (with Flow) 혹은 TypeScript 와 직접 통신할 수 있게 됩니다. 결과적으로 Codegen
은 브릿지를 제거함으로써, JavaScript와 네이티브 코드 간 통신을 더 빠르고 안정적으로 만들어줍니다.
Codegen은 결국...
- JSI를 위해 타입 안정성을 확보하고,
- TurboModule과 Fabric을 위해 네이티브 코드를 자동으로 생성합니다.
그렇다면, 왜 이 모든 작업을 빌드 시점에 미리 수행할까요? 🤔
기존의 구 아키텍처에서는 브릿지를 통해 런타임마다 데이터를 직렬화·역직렬화해야 했기 때문에, 요청이 많아질수록 병목 현상이 쉽게 발생하는 구조였습니다.
반면, Codegen
은 빌드 시점에 모든 JS ↔ Native 연결을 정적으로 고정하고, 필요한 네이티브 코드를 미리 생성해두기 때문에, 런타임에는 JSI
가 직접 메모리 주소를 참조해 빠르게 함수를 호출할 수 있게 됩니다.
이처럼 Codegen
의 전 과정을 거치면, 성공적으로 빌드를 완료하게 되며 앱 실행 준비를 마치게 됩니다! +_+ 🎉
다음에는 런타임 시 어떤 과정이 이루어지는지 알아볼텐데요,
바로 다음 포스팅에서 위에서 계속 언급되는 JSI
에 대해 파악해보는 시간을 가져보겠습니다.
그럼 다음 포스팅에서 만나요~~ 🥹👋
참고
https://reactnative.dev/docs/the-new-architecture/what-is-codegen