React Native 라이브러리 이해하기 - 2. 라이브러리 구조를 이해하자

jiveloper·2025년 2월 1일
0
post-thumbnail

이전 포스팅에서는 라이브러리를 이해하기에 앞서 라이브러리 프로젝트를 생성하는 시간을 가졌습니다. 🥳

이번 포스팅에서는 라이브러리 프로젝트의 폴더 및 파일 구조를 살펴보며, 모듈을 정의하는 코드를 살펴보겠습니다! +_+



폴더 및 파일 구조 이해하기

먼저 프로젝트의 폴더 및 파일 구조부터 살펴보겠습니다.

크게 보면 android, example, ios, src 이렇게 폴더가 구성된 걸 볼 수 있습니다.

/test-library
  ├── ...
  ├── android
  ├── example
  ├── ios
  ├── src
  └── ...

하나하나 어떻게 구성되어있는지 살펴보겠습니다!! 🤩 🤩

example

먼저 example 폴더부터 살펴보겠습니다.
example 폴더는 라이브러리가 쓰일 프로젝트의 예시를 드러낸 폴더입니다.

App.tsx는 react native 가장 최상단인 index.js에서 선언하는 파일입니다.
위 코드는 react-native-test-library 라이브러리에서 multipy라는 메서드를 꺼내 result를 반환하는 코드임을 알 수 있습니다.

multiply는 라이브러리 프로젝트에서 기본으로 제공해주는 메서드인데 어디에 정의 되어있을까요?


src

바로바로 여깁니다~^^

src 폴더 내부에는 라이브러리 모듈 인터페이스를 정의해놓는 파일이 있습니다.

/test-library/src/
  ├── NativeTestLibrary.ts
  └── index.tsx

src 안에는 NativeTestLibrary.ts, index.tsx 파일 두개가 있습니다.
각 파일에 대해 알아봅시다. 😎 😎


index

가장 먼저 src 폴더의 진입점인 index 파일부터 보겠습니다.

import TestLibrary from './NativeTestLibrary';

export function multiply(a: number, b: number): number {
  return TestLibrary.multiply(a, b);
}

잘은 모르겠지만 multiply 메서드는 NativeTestLibrary 폴더에서 multiply라는 메서드를 리턴하는걸 볼 수 있습니다.

NativeTestLibrary가 어떻게 되어있는지 바로 구경해봅시다! +_+


NativeTestLibrary

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

전문은 이렇고 하나하나 살펴보겠습니다!


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

저는 앞에서 라이브러리 프로젝트를 생성할 때, 모듈 옵션으로 Turbo 모듈 옵션을 선택하였기 때문에 TurboModule을 기반으로한 인터페이스가 생성되었습니다. TurboModule은 네이티브 모듈과 JavaScript 간의 브릿지를 제공하여 네이티브 기능을 JavaScript에서 사용할 수 있게 합니다.


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

여기서 TurboModuleRegistry는 React Native에서 TurboModule을 등록하고 가져오는 데 사용되는 레지스트리입니다. TurboModuleRegistry를 통해 특정 TurboModule을 가져오거나 등록할 수 있습니다.

TurboModuleRegistry에서 'TestLibrary'라는 이름의 TurboModule을 가져오고, 이를 Spec 타입으로 강제합니다. 이 모듈을 기본 내보내기로 설정합니다.


src 폴더는 그럼 메서드 인터페이스가 정의되어있다는 것은 알았는데...
그렇다면 메서드 정의는 어디에 되어있을까요~? 🤔 🤔

지금 하고 있는 건 크로스플랫폼 라이브러리 구현이니까, 메서드는 각 OS 별로 다르겠죠? (*OS마다 호출하는 함수가 다를 거니까!)

메서드가 어떻게 정의 되어있는지 네이티브 폴더로 가보겠습니다!


Android

먼저 android 부터 보겠습니다.

저는 라이브러리 이름을 test-library로 만들었더니 각 파일 이름이 라이브러리 이름을 따라 파스칼 형식으로 저장된 걸 볼 수 있습니다!

/test-library/android/src/main/java/com/testlibrary/
  ├── TestLibraryModule.kt
  └── TestLibraryPackage.kt

각 파일에는 코드가 어떻게 기본으로 세팅되어있는지 확인해봅시다.


TestLibraryPackage

먼저 TestLibraryPackage부터 보겠습니다!

TestLibraryPackage는 네이티브 모듈을 React Native 애플리케이션에 등록하는 역할을 합니다.
React Native 애플리케이션이 시작될 때, TestLibraryPackage가 로드되고, 이 패키지에서 제공하는 네이티브 모듈들이 등록됩니다.

코드 전문을 보겠습니다. 😌 😌

class TestLibraryPackage : BaseReactPackage() {
  override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
    return if (name == TestLibraryModule.NAME) {
      TestLibraryModule(reactContext)
    } else {
      null
    }
  }

  override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
    return ReactModuleInfoProvider {
      val moduleInfos: MutableMap<String, ReactModuleInfo> = HashMap()
      moduleInfos[TestLibraryModule.NAME] = ReactModuleInfo(
        TestLibraryModule.NAME,
        TestLibraryModule.NAME,
        false,  // canOverrideExistingModule
        false,  // needsEagerInit
        true,  // hasConstants
        false,  // isCxxModule
        true // isTurboModule
      )
      moduleInfos
    }
  }
}

이제 getModule 메서드부터 살펴봅시다!

 override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
    return if (name == TestLibraryModule.NAME) {
      TestLibraryModule(reactContext)
    } else {
      null
    }
  }

이 getModule 메서드는 React Native 애플리케이션에서 네이티브 모듈을 호출할 때 호출되어, 모듈의 이름을 기반으로 TestLibraryModule 인스턴스를 생성하고 반환합니다.


다음은 getReactModuleInfoProvider 메서드 입니다.

  override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
    return ReactModuleInfoProvider {
      val moduleInfos: MutableMap<String, ReactModuleInfo> = HashMap()
      moduleInfos[TestLibraryModule.NAME] = ReactModuleInfo(
        TestLibraryModule.NAME,  // 모듈 이름
        TestLibraryModule.NAME, // 모듈 이름
        false,  // canOverrideExistingModule (기존 모듈 덮어쓰기 가능 여부)
        false,  // needsEagerInit (애플리케이션 시작 시 초기화 필요 여부)
        true,  // hasConstants (상수 포함 여부)
        false,  // isCxxModule (C++ 모듈 여부)
        true // isTurboModule (TurboModule 여부)
      )
      moduleInfos
    }
  }

getReactModuleInfoProvider 메서드는 ReactModuleInfoProvider를 반환하여 네이티브 모듈의 정보를 React Native에 등록하는 데 사용됩니다.

ReactModuleInfoProvider는 네이티브 모듈의 이름, 초기화 방식, 상수 제공 여부, TurboModule 여부 등의 정보를 포함하는 ReactModuleInfo 객체를 제공합니다.

('C++ 모듈 여부'는 라이브러리를 생성할 때 언어 선택 부분에서 공유 C++ 라이브러리(Android 및 iOS용 C++)를 사용할지 선택하면 기본 true로 설정되어있습니다.)


TestLibraryModule

TestLibraryPackage에서 React Native에 모듈을 등록했으니...
등록할 모듈을 만들어보겠습니다!! 😎 😎

먼저 전문을 살펴볼까요? 드디어 라이브러리 모듈 공개...! (두둥 +_+)

@ReactModule(name = TestLibraryModule.NAME)
class TestLibraryModule(reactContext: ReactApplicationContext) :
  NativeTestLibrarySpec(reactContext) {

  override fun getName(): String {
    return NAME
  }

  // Example method
  // See https://reactnative.dev/docs/native-modules-android
  override fun multiply(a: Double, b: Double): Double {
    return a * b
  }

  companion object {
    const val NAME = "TestLibrary"
  }
}

TestLibraryModule 클래스는 JavaScript와 네이티브 코드 간의 상호 작용을 가능하게 하는 브릿지의 중요한 부분입니다. JavaScript 코드에서 TestLibrary 모듈을 호출하면, React Native의 TurboModuleJSI를 통해 이 클래스의 메서드가 실행됩니다.
(저는 앞에서 라이브러리 프로젝트를 생성할 때, 모듈 옵션으로 Turbo 모듈 옵션을 선택하였기 때문이죵!)

기본으로 multiply라는 메서드가 제공되는데,

override fun multiply(a: Double, b: Double): Double {
	return a * b
}

매개변수로 Double 타입의 a, b의 수를 받아 곱하는 메서드입니다.


여기까지가! 안드로이드에서 네이티브 모듈을 등록하는 코드입니다.
그래서 모듈과의 통신을 어떻게 하는지는 ios까지 코드 확인해보고 뒤에서 살펴보겠습니다. >,<



iOS

android까지 훑어봤으니 iOS도 금방 볼 수 있을 겁니다!!!
(그렇다고 해주세요. 초롱초롱 +_+)

딱봐도! iOS는 android보다 덜 복잡한거 보이시죠?!! 폴더도 더 없고 파일 두개가 끝! 입니다.

라이브러리 이름을 test-library로 만들었더니 iOS도 android처럼 각 파일 이름이 라이브러리 이름을 따라 파스칼 형식으로 저장된 걸 볼 수 있습니다!

/test-library/ios/
  ├── TestLibrary.h
  └── TestLibrary.mm

각 파일에는 코드가 어떻게 기본으로 세팅되어있는지 확인해봅시다.

TestLibrary.h

TestLibrary 헤더 파일은 모듈이 정의 될 TestLibrary 클래스의 인터페이스를 정의합니다.

코드 전문을 보겠습니다!

#import "generated/RNTestLibrarySpec/RNTestLibrarySpec.h"

@interface TestLibrary : NSObject <NativeTestLibrarySpec>

@end

짜잔! 3줄이 다입니다. 🫢 🫢

이 파일은 TestLibrary 클래스의 인터페이스를 정의하는 코드입니다. 이 클래스는 NSObject (Objective-C에서 가장 기본이 되는 클래스)를 상속받고, NativeTestLibrarySpec 프로토콜을 준수합니다.

(NativeTestLibrarySpec은 RNTestLibrarySpec 헤더 파일에 있으며, RNTestLibrarySpec 헤더 파일은 빌드하면 자동으로 생성되는 파일입니다.)

인터페이스를 정의해놓는 것이 test-library/src/NativeTestLibrary.ts의 Spec과 동일한 역할을 합니다.

자~ 그렇다면 TestLibrary 모듈은 어떻게 구성되어있는지 볼까요?


TestLibary.mm

android에서는 TestLibraryPackage에서 네이티브 모듈을 React Native 애플리케이션에 등록하는 역할을 했죠? iOS에서는 TestLibrary.mm 파일의 TestLibrary 클래스가 동일한 역할을 합니다.

코드 전문부터 보겠습니다!

#import "TestLibrary.h"

@implementation TestLibrary
RCT_EXPORT_MODULE()

- (NSNumber *)multiply:(double)a b:(double)b {
    NSNumber *result = @(a * b);

    return result;
}

- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
    (const facebook::react::ObjCTurboModule::InitParams &)params
{
    return std::make_shared<facebook::react::NativeTestLibrarySpecJSI>(params);
}

@end

(어웅 복잡해~~~😵😵 objective-c가 익숙치 않은 저같은 개발자들은 위 코드가 다소 낯설 수도 있습니다. 킹치만...! 차근차근 보면 됩니다...! )


ㅠㅠ

바로 한줄한줄 해석해보겠습니다~!

#import "TestLibrary.h"

위에서 설명드린 TestLibrary.h 헤더 파일을 포함합니다.


RCT_EXPORT_MODULE()

이 부분이 ✌️포인트✌️인데, 이 매크로는 TestLibrary 모듈을 React Native에 등록하는 부분입니다. 이 매크로를 사용하면 JavaScript 코드에서 이 모듈을 사용할 수 있게 됩니다. android에서 getReactModuleInfoProvider 메서드와 같은 역할입니다!!


- (NSNumber *)multiply:(double)a b:(double)b {
    NSNumber *result = @(a * b);
    return result;
}

위 코드는 android와 마찬가지로 기본으로 제공하는 multiply 메서드 입니다. 매개변수로 double 타입의 a, b의 수를 받아 곱해 NSNumber 객체로 반환하는 메서드입니다.


- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
    (const facebook::react::ObjCTurboModule::InitParams &)params
{
    return std::make_shared<facebook::react::NativeTestLibrarySpecJSI>(params);
}

getTurboModule 메서드는 TurboModule을 초기화하고 반환합니다. 이 메서드는 TurboModule을 사용하여 JavaScript와 네이티브 코드 간의 상호 작용을 가능하게 합니다. NativeTestLibrarySpecJSI 클래스는 TurboModule의 구현을 담당합니다.


어웅... 넘 복잡스럽고 어려웠습니다... 😭 😭

but...!!

다음 포스팅에서 모듈 통신을 이해하는 과정을 위 코드와 함께 차근차근 다시 살펴볼 것입니다! +_+

그럼 다음 포스팅에서 만나요!! 👋👋

profile
👩🏻‍💻 크로스플랫폼 앱 개발자입니다.

0개의 댓글

관련 채용 정보