react-native는 Meta(Facebook)가 만든 오픈 소스 프레임워크다.
JS와 React를 사용해서 iOS/Android App을 개발할 수 있다.
두 OS를 모두 개발할 수 있다니! 너무나 큰 장점이지만,
동시에 두 OS마다 각각 다른 에러를 만드는 단점이기도 하다.
지난 2년 동안 react-native와 동고동락하며 쌓아온 에러 대응 꿀팁을 공유하려 한다.
최대한 모든 RN 버전, OS, 상황을 대입해 가정 기본적인 대응부터 에러의 원인을 유추하고,
추가적으로 라이브러리의 코드가 수정이 필요한 경우 대처하는 방법까지 아낌없이 듬뿍 담아 적는다.
react-native 첫 시작을, 허무하게 실패하지 않을 가장 유효한 방법.
https://reactnative.dev/docs/environment-setup
enviroment-setup 문서의 사항들을 빠짐없이 체크하고, 따르면 된다.
iOS/Android 각 OS마다 설치해야 할 목록이 별개로 존재하고,
Android의 경우 셸 프로필에 접근해 추가해야 하는 내용도 있다.
🚨 설치한 xCode, Android Studio, JDK 등의 버전을 잘 체크해두자.
특정 react-native 버전과 호환이 안돼 생기는 에러가 생각보다 많다.
솔루션을 쉽게 찾으려면, 버전과 같이 검색해야 한다.
공식 문서가 모든 내용을 상세히 설명해 주지 않아 놓칠 수 있는 부분도 있다.
예를 들어 맥의 기본으로 설치된 ruby 버전은 2.6.10으로
react-native(0.71)에서 최소 버전으로 책정한 2.7.6보다 낮은 상태다.
rbenv, RVM 등 ruby version manager를 설치해 적정 버전으로 업그레이드해야 한다.
(설명해 주지만 쉽게 지나칠 수도 있음)
cocoapods 설치도 여러 방법이 있지만, 개인적으로 cocoapods은 brew에서 설치하는 걸 추천한다.
$ brew install cocoapods
보통 init 프로젝트의 빌드가 안되는 경우, 환경설정에서 문제가 있을 확률이 높다.
- 최신 버전으로 설치된 xCode, Android Studio 등이 호환이 안되는 경우
- 셸 프로필에서 JDK PATH alias 설정을 안 한 경우
- 낮은 버전 ruby 환경에서 생기는 에러
- Mac CommandLineTools 관련한 에러
- 데스크탑 OS 버전과 호환성 에러 (Apple Silicon, Window)
경험상 init 프로젝트에서 생기는 에러들은 대부분 ruby, JDK, xCode 등이 주된 원인이었다. 이 세 가지만 잘 준비하면 웬만해선 실행까지 문제가 없다.
모든 환경설정을 마무리한 후 react-native 프로젝트를 만들어 테스트해본다.
# latest 버전으로 설치
$ npx react-native@latest init AwesomeProject
# 원하는 버전을 설정해 설치
$ npx react-native@X.XX.X init AwesomeProject --version X.XX.X
$ npx react-native start
$ npx react-native run-ios
$ npx react-native run-android
해당 화면이 잘 출력된다면 init 프로젝트 실행은 성공이다.
RN을 처음 시작했던 날,
clone 한 프로젝트가 실행조차 되지 않아 울먹이며 공부를 시작했었다.
당시 0.65 버전을 Apple Silicon 환경에서 실행시키는 것이 목표였고,
부득이한 팀 상황으로 인해 혼자서 어렵게 해결했었다.
이후 유지 보수나 기능 개발 과정에서도 낮은 RN 버전으로 인해 많은 에러를 만나게 됐다.
그러면서 쌓여간 의문들
'왜 낮은 버전의 react-native는 오류가 많을까?'
'왜 react-native 생태계의 서드 파티 라이브러리들은 오류가 이렇게 많을까?'
'왜 Podfile과 build.gradle 등의 파일들을 수정하고 관리해야 할까?'
사실 이러한 의문들에 대해 통달하려면 Native 개발도 알아야 하고,
react-native 작동 원리인 브릿지, JSC, JSI(현재 최신 버전은 Hermes) 등을 알아야 한다.
하지만 react-native는 같은 UI를 가진 iOS/Android 앱을 JS 코드로 쉽게 만드는 것이 장점인데
Native 개발과 react-native 원리에도 러닝 코스트를 들이라는 팁은
아쉽게 느껴진다. 아니, 매우 아쉽다.
그러니 즉시 사용할 수 있는 잡기술을 공유한다.
위에 이미지를 통해 알 수 있는 react-native의 작동 원리를 간단히 설명하자면
- JS 코드를 작성(react.js)
- JS Bundle로 내보낸다
- xCode/gradlew(Android Studio)로 Native App을 빌드 한다
- 실행된 Native App은 JS Bundle을 로드하고, 브릿지로 JS와 Native를 연결한다
보다시피 JS의 영향력이 큰 부분과 OS 환경의 영향력이 큰 부분이 분리돼있다.
빌드가 안된다? -> iOS/Android 등 OS 환경 문제
실행이 안된다? -> 로드한 JS Bundle 혹은 JS Thread에서 발생하는 JS 코드 에러
크게 이 두 가지로 나눠볼 수 있게 된다.
그렇다면 생각할 수 있는 상황별 에러 원인 후보들은 이렇게 좁혀진다.
빌드: 추가한 라이브러리의 Native 환경, Pods, gradlew, xCode, Android Studio, OS version
실행: JS 코드(react, react-native), 추가한 라이브러리의 JS 코드
각 상황에 맞는 원인 후보들을 우선적으로 생각하며 에러를 수정한다.
하지만, 당연히 절대적인 방법은 아니다.
다른 경우의 수도 있기 때문에, 어디까지나 우선순위를 정하는 효율적인 방법이라 생각하자.
보통 빌드를 성공했다면 어려운 문제는 아니다.
하지만 RN의 역경과 고난은 빌드에서 올 확률이 높다.
우리는 빌드라는 경이와 조우하기 전, 불필요한 잡념을 거둬낼 필요가 있다.
새로 init 한 프로젝트가 아니라면 현재 디렉토리의 여러 캐시가 쌓여 있을 수 있고, 라이브러리를 새로 추가했거나, Native 코드를 수정했다면 한번 비워내는 것이 좋다.
$ cd ios
$ rm -rf Pods Podfile.lock # Pods clean
$ rm -rf ~/Library/Developer/Xcode/DerivedData # xCode clean
$ pod deintegrate
$ pod setup
# pod version 확인
$ pod --version
# cocoapods version > 1.10.0
$ pod install
# cocoapods version < 1.10.0
$ arch -x86_64 pod install
Apple Silicon이 출시되고 pod install이 제대로 되지 않는 이슈가 생겼었는데
https://github.com/CocoaPods/CocoaPods/pull/9790
1.10.0 version부터는 해결이 된 모양이다.
Rosetta 터미널을 사용할 필요도 없어졌다.
$ cd android
$ ./gradlew clean
# node_modules 삭제 후 재설치
$ rm -rf node_modules && npm or yarn install
# watchman clean
$ watchman watch-del-all
# cache-clean react-native start
$ npx react-native start --reset-cache
"scripts": {
...
"clean-install": "rm -rf node_modules && yarn install",
"pod-install": "cd ios && if [[ \\\"$(uname -m)\\\" == \\\"arm64\\\" ]]; then arch -x86_64 pod install; else pod install; fi",
"ios-clean": "cd ios && rm -rf Pods Podfile.lock && rm -rf ~/Library/Developer/Xcode/DerivgedData && pod deintegrate && pod setup",
"android-clean": "cd android && ./gradlew clean"
},
"pod-install"은 혹시나 다른 팀원이 사용하거나, 나중에 인수인계를 할 경우 cocoapods version의 의존성을 줄이기 위해 if문을 넣었다.
환경설정도 완벽하고, init 프로젝트의 빌드/실행도 문제없이 잘 돌아간다.
캐시도 깔끔하게 전부 청소했다.
하지만, 이상하게 작업하려는 RN 프로젝트만 말썽이라면?
프로젝트 안에 배신자가 있다. 그게 내 결론이다.
위에 팁들을 모두 사용했음에도 에러가 사라지질 않는다면,
이제 배신자를 적출할 시간이다.
"구 버전을 유기한 서드파티 라이브러리 개발자"
"Native 코드를 수정하고, 별도의 문서화나 주석을 달지 않은 전임자"
"무슨 일을 저질렀는지 기억 못 하는 나" (가장 중요)
실제 사례와 함께 디버깅을 체험해 보자.
0.68 버전의 RN을 0.70으로 업그레이드하려는 상황.
Android 빌드에서 해당 에러가 발생했다.
gradle version이 낮아서 생기는 문제
kotlin-android-extension이 최소 1.6.20부터 지원하기 때문에 버전을 수정하고 다시 시도했다.
어? 뭐지 적용이 안됐나?
node_modules/@flyerhq/react-native-android-uri-path/android/build.gradle
해당 gradle 파일을 열어보자
어?? kotlin_version이 아니라 kotlinVersion?
rootProject에 kotlin_version만 있어 fallback 버전인 1.6.10이 적용되고 있었다.
찾아보니 다른 라이브러리인 react-native-vision-camera에서도
def kotlin_version이 'kotlinVersion'을 찾고 있기 때문에
1.6.20이 아닌 VisionCamera_kotlinVersion=1.5.30이 적용되고 있었다.
기도하는 마음으로 rootProject.ext에 camelCase인 kotlinVersion을 추가하고 수정해 봤지만,
그래 너는 또 snake_case구나
https://github.com/teslamotors/react-native-camera-kit/issues/511
react-native-camera-kit을 사용하려면,
위 이슈처럼 rootProject build.gradle 파일에 kotlin version을 두 번 작성해 줘야 한다.
만약 이러한 과정들을 급하게 수정만 하고,
별도의 문서 작성이나 주석을 달지 않으면 모두가 건드리기 어려운 코드가 된다.
흔히 말하는
"이거 왜 그래요?" "모르겠는데요?"
느낌표 없이 물음표만 늘어나는 미래가 남는다.
실제로 여러 react-native 서드파티 라이브러리들이 이러한 레거시들을 많이 갖고 있으며,
해결하는 방법 또한 RN 버전 업그레이드 / 라이브러리 버전 업그레이드를 제시하고 있다.
하지만 프로덕트 상황상 구 버전을 유지해야 하는 개발자가 듣기에는 너무나 매정한 조언이다.
그래서 다음 편에는 RN 버전과 라이브러리 버전 업그레이드를 보수적으로 접근하기 위해
patch-package를 활용해서 임시로 라이브러리 코드를 수정해서 사용하거나,
React Native Upgrade Helper를 사용해 안전하게 버전 업을 시도하는 방법을 소개하려고 한다.