웹 개발 할 때는 node_modules랑 package-lock.json 싹 지우고 다시 깔면(Re-install) 만병통치약처럼 해결되곤 했다.
하지만 React Native(RN)에서 락 파일(lock file)을 지우는 건 매우 민감하다.
즉, package-lock.json이 있으면 모두 똑같은 node_modules가 만들어진다. 만약 lock 파일이 없다면? 다음의 상황을 예로 들어보자.
이때 lock 파일을 삭제하고 pnpm install 을 한다면? 내가 라이브러리 A를 유지했더라도, 라이브러리B가 몰래 2.1.0으로 설치된다. 내 눈에는 안보이지만 node_modules 구조가 바뀌어버리는 것이다. RN은 js 라이브러리들이 서로 얽혀있어서, 이 하위 의존성 하나가 바뀌면 전체 빌드가 깨지는 경우가 매우 흔하다.
React Native 라이브러리는 Java / Kotlin (Android) 코드와 Objective-C/Swift (iOS) 코드를 포함한다. node_modules에 설치된 버전에 맞춰서 Gradle이나 CocoaPods가 네이티브 코드를 컴파일한다. 이때 일부 라이브러리 버전이 미세하게 바뀌면?
javascript 코드는 v1.0의 기능을 호출하는데, 네이티브 코드는 v1.1로 설치되면서 메서드 이름이 바뀌었거나 파일 위치가 달라질 수 있다. 결국 빌드 에러가 나거나, 앱이 켜지자 마자 크래시가 발생한다. 특히 애니메이션이나 제스처처럼 사용자 인터렉션과 관련된 라이브러리들이 매우 민감하다.
react, react-native, @react-navigation/*, react-native-reanimated, react-native-gesture-handler 등 서로 정확한 버전을 요구하는 경우가 많다.
1. lock 파일 없으면 자동으로 최신 버전 끌어오면서 peer dependency 경고가 뜸.
2 런타임 크래시발생 가능성 높음
의존성 버전이 미세하게 바뀌면 metro 캐시가 깨져서 번들링 실패하거나 hermes bytecode와 호환 안 맞아서 앱이 죽을 수 있다.
RN 에서는 package-lock.json을 지울일은 거의 없다. 단일 시스템으로 작동되는 웹과 달리 크로스 플랫폼인 RN은 버전관리가 매우 어렵기 때문이다. 웹에서의 습관처럼 오류가 나면 package-lock.json을 지우기보단 node_modules 폴더를 지우거나 pnpm start --reset-cache를 시도해보자.
React Native 생태계에서 react-native-reanimated, react-native-gesture-handler, react-native-screens 이 3대장이 가장 민감하고 에러가 자주 터지는 라이브러리들이다. 단순히 코드가 복잡해서가 아니다. React Native의 구조적 한계를 뚫으려고 네이티브 시스템 깊숙한 곳(C++)까지 건드리기 때문이다.
왜 유독 이 라이브러리들이 민감한지, 기술적인 이유 3가지를 정리해보자.
일반적인 RN 동작
JS 스레드에서 로직 실행 → JSI/Fabric을 통해 네이티브 UI 스레드에 렌더링 명령한다. 대부분의 UI는 이것만으로도 충분히 빠름 (JSI 덕분)
애니메이션/제스처의 문제
사용자가 화면을 휙 스크롤 하거나 드래그할 때, 0.016초(60fps) 안에 반응해야 한다. 근데 JS 스레드가 바쁘면(데이터 가공 등) 버벅거림이 발생한다. 특히 복잡한 애니메이션은 매 프레임마다 계산이 필수적이다.
해결책 (Reanimated 등)
이 라이브러리들은 성능을 위해 JS 스레드를 우회하고 네이티브 UI 스레드에서 직접 코드를 실행시킨다.
plugins: ['react-native-reanimated/plugin'],내가 작성한 js 코드를 컴파일단계에서 가로채서 변형시키는 역할을 한다. 이 덕분에 JS 스레드가 바쁘거나 멈춰도 애니메이션은 부드럽게 계속 진행될 수 있다.이들은 설치될 때 Android의 C++/Java 코드와 iOS의 C++/Objective-C 코드가 같이 컴파일되어 앱에 심어진다. 만약 lock 파일 삭제 시,
하지만 Reanimated는 여전히 특별함:
1. Worklet 시스템: UI 스레드에서 JS 코드 직접 실행 (다른 모듈은 안 함)
2. Shared Value: JS-네이티브 간 메모리 공유 (특수한 C++ 구조)
3. 동기적 상태 동기화: 일반 JSI보다 더 깊은 레벨의 통합
결국 New Architecture든 구 아키텍처든, Reanimated는 RN의 가장 깊숙한 곳을 건드리기 때문에 여전히 가장 민감한 라이브러리다.