Skip to main content

React Native Performance in Marketplace

· 10 min read
Software Engineer at Facebook

React Native는 메인 Facebook 앱의 상단 탭을 포함해 Facebook 계열의 여러 앱에서 다양하게 활용된다. 이 글에서 중점적으로 살펴볼 제품은 Marketplace다. Marketplace는 약 12개 국가에서 서비스되며, 사용자가 다른 사용자가 제공하는 상품과 서비스를 발견할 수 있도록 도와준다.

2017년 상반기에 Relay 팀, Marketplace 팀, Mobile JS Platform 팀, 그리고 React Native 팀의 협력을 통해 Year Class 2010-11 기기에서의 Marketplace Time to Interaction(TTI)를 절반으로 줄였다. Facebook은 이 기기들을 저사양 안드로이드 기기로 분류하며, 이 기기들은 모든 플랫폼 또는 기기 타입 중 가장 느린 TTI를 보여준다.

일반적인 React Native 시작 과정은 다음과 같다:

주의: 비율은 대표적이지 않으며, React Native가 어떻게 구성되고 사용되는지에 따라 달라진다.

먼저 React Native 코어(일명 "Bridge")를 초기화한 후, 제품별 JavaScript를 실행한다. 이 JavaScript는 Native Processing Time 동안 React Native가 어떤 네이티브 뷰를 렌더링할지 결정한다.

다른 접근 방식

초기에 우리가 저지른 실수 중 하나는 Systrace와 CTScan이 성능 개선 작업을 주도하도록 내버려 둔 것이다. 이 도구들은 2016년에 많은 쉬운 문제들을 찾는 데 도움을 주었지만, Systrace와 CTScan이 실제 프로덕션 환경을 대표하지 못하며 실제 상황에서 발생하는 문제를 제대로 재현할 수 없다는 사실을 깨달았다. 시간 분할 비율이 종종 틀리거나 심지어 전혀 엉뚱한 경우도 있었다. 극단적인 경우, 우리가 몇 밀리초 정도 걸릴 것으로 예상했던 작업들이 실제로는 수백 또는 수천 밀리초가 걸리기도 했다. 그렇지만 CTScan은 여전히 유용하며, 우리는 이 도구가 프로덕션 환경에 도달하기 전에 약 1/3의 성능 저하 문제를 잡아낸다는 사실을 발견했다.

안드로이드에서 이러한 도구들의 한계는 1) React Native가 멀티스레드 프레임워크라는 점, 2) Marketplace가 뉴스피드와 같은 복잡한 뷰 및 다른 최상위 탭과 함께 배치된다는 점, 3) 계산 시간이 크게 변동한다는 사실에 기인한다. 따라서 이번 반기에는 프로덕션 측정과 분석 결과가 거의 모든 의사결정과 우선순위 설정을 주도하도록 했다.

프로덕션 계측 과정의 여정

프로덕션 계측은 표면적으로는 간단해 보이지만, 실제로는 꽤 복잡한 과정이다. 각각 2-3주에 걸친 여러 번의 반복 주기를 거쳤다. 마스터 브랜치에 커밋을 적용하고, 앱을 Play Store에 푸시하고, 충분한 프로덕션 샘플을 수집해 작업에 대한 확신을 얻기까지의 지연 시간 때문이다. 각 반복 주기에서는 분석이 정확한지, 적절한 수준의 세분화를 가지고 있는지, 전체 시간 범위에 올바르게 합산되는지 확인했다. 알파와 베타 릴리스는 일반 사용자를 대표하지 않기 때문에 의존할 수 없었다. 결국, 우리는 수백만 개의 샘플을 집계해 매우 정확한 프로덕션 트레이스를 매우 세심하게 구축했다.

분석에서 매 밀리초가 상위 메트릭에 올바르게 합산되는지 꼼꼼히 확인한 이유 중 하나는 초기에 계측에 공백이 있음을 깨달았기 때문이다. 초기 분석은 스레드 점프로 인한 지연을 고려하지 않았다. 스레드 점프 자체는 비용이 크지 않지만, 이미 작업 중인 바쁜 스레드로의 점프는 매우 비용이 크다. 결국, 적절한 순간에 Thread.sleep() 호출을 추가해 이러한 차단을 로컬에서 재현했고, 다음과 같은 방법으로 문제를 해결했다:

  1. AsyncTask에 대한 의존성을 제거했다.
  2. UI 스레드에서 ReactContext와 NativeModules의 강제 초기화를 취소했다.
  3. 초기화 시점에 ReactRootView를 측정하는 의존성을 제거했다.

이러한 스레드 차단 문제를 해결함으로써 시작 시간을 25% 이상 단축했다.

프로덕션 메트릭은 우리의 기존 가정 중 일부에 도전하기도 했다. 예를 들어, 시작 경로에서 많은 JavaScript 모듈을 미리 로드했는데, 이는 모듈을 하나의 번들에 함께 배치하면 초기화 비용이 줄어든다는 가정 때문이었다. 그러나 이러한 모듈을 미리 로드하고 함께 배치하는 비용은 이점을 훨씬 초과했다. 인라인 require 블랙리스트를 재구성하고 시작 경로에서 JavaScript 모듈을 제거함으로써, Relay Classic과 같은 불필요한 모듈을 로드하지 않을 수 있었다(Relay Modern만 필요한 경우). 현재, RUN_JS_BUNDLE 분석은 75% 이상 빨라졌다.

제품별 네이티브 모듈을 조사해 얻은 성과도 있었다. 예를 들어, 네이티브 모듈의 의존성을 지연 로딩함으로써 해당 모듈의 비용을 98% 줄였다. Marketplace 시작을 다른 제품과의 경합에서 제거함으로써, 동일한 시간만큼 시작 시간을 단축했다.

가장 좋은 점은 이러한 개선 사항 중 상당수가 React Native로 구축된 모든 화면에 광범위하게 적용될 수 있다는 것이다.

결론

많은 사람들이 React Native의 시작 성능 문제를 자바스크립트의 속도가 느리거나 네트워크 시간이 과도하게 길기 때문이라고 생각한다. 물론 자바스크립트 속도를 높이면 TTI(Time to Interactive)를 상당히 줄일 수 있지만, 이전에 생각했던 것보다 이러한 요소들이 TTI에 미치는 영향은 훨씬 작다.

지금까지의 교훈은 측정, 측정, 또 측정이다! 일부 성능 개선은 런타임 비용을 빌드 시간으로 옮기는 방식에서 얻을 수 있다. 예를 들어 Relay Modern과 Lazy NativeModules가 그렇다. 다른 개선은 코드를 병렬화하거나 죽은 코드를 제거하는 방식으로 작업을 피함으로써 얻을 수 있다. 또한 React Native의 대규모 아키텍처 변경, 예를 들어 스레드 블로킹을 정리하는 것과 같은 방법으로도 성능을 개선할 수 있다. 성능 문제에 대한 만능 해결책은 없으며, 장기적인 성능 개선은 점진적인 도구화와 개선을 통해 이루어진다. 인지 편향이 결정에 영향을 미치지 않도록 주의해야 한다. 대신, 실제 프로덕션 데이터를 신중하게 수집하고 해석하여 미래 작업을 이끌어야 한다.

향후 계획

장기적으로는 Marketplace TTI가 네이티브로 개발된 유사 제품과 비슷한 수준이 되길 바라며, React Native의 성능이 네이티브 수준에 근접하도록 개선할 계획이다. 또한, 이번 분기에 브릿지 시작 비용을 약 80%나 줄였지만, Prepack과 같은 프로젝트를 통해 React Native 브릿지 비용을 거의 0에 가깝게 낮추려고 한다. 더불어 빌드 시간 처리도 더욱 최적화할 예정이다.

React Native Monthly #2

· 14 min read
Tomislav Tenodi
Shoutem 제품 매니저

React Native 월간 미팅이 계속됩니다! 이번 세션에서는 Chain React, the React Native Conference를 주최한 Infinite Red가 함께했습니다. 대부분의 참가자들이 Chain React에서 발표를 준비 중이었기 때문에, 미팅을 일주일 뒤로 미뤘습니다. 컨퍼런스의 발표 내용은 온라인에 업로드되었으니, 꼭 확인해 보세요. 이제 각 팀이 어떤 일을 하고 있는지 살펴보겠습니다.

참여 팀

두 번째 미팅에는 총 9개 팀이 참여했다:

노트

각 팀의 노트는 다음과 같다:

Airbnb

  • React Native 관련 프로젝트를 확인하려면 Airbnb 저장소를 방문한다.

콜스택 업데이트

  • Mike Grabowski는 여전히 React Native의 월별 릴리스를 관리하고 있으며, 몇 가지 베타 버전도 배포했다. 특히 Windows 사용자를 위한 v0.43.5 빌드를 npm에 출시하기 위해 노력 중이다.

  • Haul 프로젝트는 느리지만 꾸준히 진행 중이다. HMR(Hot Module Replacement)을 추가하는 풀 리퀘스트가 있으며, 다른 개선 사항도 적용되었다. 최근 몇몇 업계 리더들이 이를 도입했다. 해당 분야에서 풀타임 유료 작업을 시작할 계획도 있다.

  • Jest 팀의 Michał Pierzchała가 이번 달에 콜스택에 합류했다. 그는 Haul 유지보수를 도울 예정이며, Metro BundlerJest 작업에도 참여할 가능성이 있다.

  • Satyajit Sahoo가 이제 우리와 함께하게 되었다!

  • OSS 부서에서 다양한 멋진 작업이 진행 중이다. 특히 Material Palette API를 React Native로 가져오는 작업을 진행하고 있다. 네이티브 iOS 컴포넌트와 1:1로 동일한 느낌을 제공하는 네이티브 iOS 키트를 마침내 출시할 계획이다.

Expo 최신 소식

  • 최근 React Native 생태계 내 라이브러리의 발견과 평가를 돕기 위해 Native Directory를 출시했다. 문제는 많은 라이브러리가 존재하지만 테스트가 어렵고, 수동으로 휴리스틱을 적용해야 하며, 어떤 라이브러리가 가장 좋은지 즉시 알기 어렵다는 것이다. 또한 CRNA/Expo와 호환되는지 여부를 파악하기도 어렵다. Native Directory는 이러한 문제를 해결하려고 한다. 여기에서 라이브러리 목록을 확인할 수 있다. 이는 첫 번째 시도이며, Expo 팀뿐만 아니라 커뮤니티가 주도적으로 운영하길 바란다. 이 프로젝트가 가치 있다고 생각한다면 기여해 주길 바란다!
  • Snack에서 Expo SDK 19와 함께 npm 패키지를 설치할 수 있는 초기 지원을 추가했다. 문제가 발생하면 알려주길 바란다. 아직 몇 가지 버그를 수정 중이다. Native Directory와 함께 JS 의존성만 있거나 Expo SDK에 포함된 의존성을 가진 라이브러리를 쉽게 테스트할 수 있어야 한다. 다음 예제를 시도해 보라:
  • Expo SDK19를 출시하며 다양한 개선 사항을 적용했고, 업데이트된 Android JSC를 사용하고 있다.
  • Alexander Kotliarskyi와 함께 앱의 사용자 경험을 개선하는 방법에 대한 팁 목록을 포함한 가이드를 작성 중이다. 이 목록에 추가하거나 작성에 도움을 주길 바란다!
  • 오디오/비디오, 카메라, 제스처(Software Mansion의 react-native-gesture-handler와 협력), GL 카메라 통합 작업을 계속 진행 중이며, SDK20(8월)에서 처음으로 이 기능들을 제공할 계획이다. 또한 이때까지 다른 기능들도 크게 개선할 예정이다. Expo 클라이언트에 백그라운드 작업(위치 정보, 오디오, 알림 처리 등)을 위한 인프라 구축을 시작했다.
  • Adam Miskiewiczreact-navigation에서 UINavigationController의 트랜지션을 모방하는 데 큰 진전을 이루었다. 그의 트윗에서 초기 버전을 확인할 수 있다. 곧 출시될 예정이다. 또한 그가 업스트림MaskedViewIOS도 확인해 보라. MaskedView를 Android에 구현할 능력과 의지가 있다면 매우 환영한다!

Facebook

  • Facebook은 내부적으로 React Native 안에 네이티브 ComponentKitLitho 컴포넌트를 임베드할 수 있는 방법을 연구하고 있다.
  • React Native에 기여하는 것은 매우 환영한다! 기여 방법이 궁금하다면, "How to Contribute" 가이드를 참고해 개발 프로세스와 첫 풀 리퀘스트를 보내는 단계를 확인할 수 있다. 코드 작성 없이도 기여할 수 있는 방법이 있다. 이슈를 분류하거나 문서를 업데이트하는 것도 그 예시다.
    • 현재 React Native에는 635개오픈 이슈249개오픈 풀 리퀘스트가 있다. 이는 관리자들에게 부담이 되며, 내부적으로 문제가 해결되더라도 관련 작업이 업데이트되기 어려운 상황이다.
    • 커뮤니티를 만족시키면서 이 문제를 해결할 최선의 방법을 아직 확정하지 못했다. 몇 가지 옵션(모든 것이 아님!)으로는 오래된 이슈를 닫기, 더 많은 사람에게 이슈 관리 권한 부여하기, 이슈 템플릿을 따르지 않는 이슈를 자동으로 닫기 등이 있다. 관리자들이 기대하는 바를 명확히 하고 예상치 못한 상황을 피하기 위해 "What to Expect from Maintainers" 가이드를 작성했다. 관리자와 이슈 및 풀 리퀘스트를 열어주는 사람들이 모두 만족할 수 있는 방법에 대한 아이디어가 있다면 알려주기 바란다!

GeekyAnts

  • Chain React에서 React Native 파일과 호환되는 디자이너 툴을 시연했다. 많은 참가자들이 대기자 명단에 등록했다.
  • Google Flutter, Kotlin Native, Apache Weex와 같은 크로스 플랫폼 솔루션도 살펴보고 있다. 이들의 아키텍처 차이를 이해하고, React Native의 전반적인 성능을 개선할 수 있는 방법을 모색 중이다.
  • 대부분의 앱에서 react-navigation으로 전환했고, 이로 인해 전반적인 성능이 향상되었다.
  • 또한 NativeBase Market을 발표했다. 이는 React Native 컴포넌트와 앱을 위한 마켓플레이스로, 개발자들이 직접 참여하고 활용할 수 있는 플랫폼이다.

Infinite Red

마이크로소프트

  • CodePush가 이제 Mobile Center에 통합되었다. 기존 사용자는 작업 흐름에 변화가 없다.
    • 일부 사용자가 중복 앱 문제를 보고했다. Mobile Center에 이미 앱이 존재하는 경우다. 이 문제를 해결 중이며, 두 개의 앱이 있다면 알려주면 병합해 줄 수 있다.
  • Mobile Center는 이제 CodePush용 푸시 알림을 지원한다. 또한 알림과 CodePush를 조합해 A/B 테스트를 수행하는 방법을 보여주었다. 이는 ReactNative 아키텍처에서만 가능한 독특한 기능이다.
  • VS Code에는 ReactNative 디버깅 문제가 알려져 있다. 며칠 내로 출시될 다음 확장 버전에서 이 문제가 해결될 예정이다.
  • 마이크로소프트 내부에서도 여러 팀이 React Native를 개발하고 있다. 다음 회의에서는 모든 그룹의 참여를 더 잘 반영할 수 있도록 노력할 것이다.

Shoutem

  • Shoutem에서 React Native 개발을 더 쉽게 만드는 과정을 완료했다. Shoutem에서 앱을 개발할 때 모든 표준 react-native 커맨드를 사용할 수 있다.
  • React Native에서 프로파일링을 최적으로 수행하는 방법을 찾기 위해 많은 노력을 기울였다. 문서의 상당 부분이 오래되어, 공식 문서에 풀 리퀘스트를 보내거나 최소한 블로그 포스트에 우리의 결론을 작성할 계획이다.
  • 네비게이션 솔루션을 react-navigation으로 전환했으므로, 곧 피드백을 얻을 수 있을 것이다.
  • 툴킷에 새로운 HTML 컴포넌트를 출시했다. 이 컴포넌트는 원시 HTML을 React Native 컴포넌트 트리로 변환한다.

Wix

  • Metro Bundlerreact-native-repackager 기능을 추가하는 풀 리퀘스트 작업을 시작했다. 프로덕션에서 사용하는 RN 44를 지원하도록 react-native-repackager를 업데이트했다. 이를 detox의 모킹 인프라에 활용하고 있다.
  • 지난 3주 동안 Wix 앱을 detox 테스트로 커버하고 있다. 40명 이상의 엔지니어가 참여하는 대규모 앱에서 수동 QA를 줄이는 방법에 대해 많은 것을 배울 수 있었다. 그 과정에서 detox의 여러 문제를 해결했고, 새로운 버전을 출시했다. "제로 불안정성 정책"을 충실히 지키고 있으며, 현재까지 테스트가 지속적으로 통과되고 있다는 점을 기쁘게 알린다.
  • Detox for Android는 순조롭게 진행 중이다. 커뮤니티로부터 큰 도움을 받고 있다. 약 2주 내로 초기 버전을 출시할 예정이다.
  • 성능 테스트 도구인 DetoxInstruments는 원래 계획보다 더 큰 규모로 발전하고 있다. 이제 이를 detox와 긴밀하게 결합되지 않은 독립형 도구로 전환할 계획이다. 이를 통해 iOS 앱의 성능을 일반적으로 조사할 수 있게 될 것이다. 또한 detox와 통합해 성능 지표에 대한 자동화 테스트를 실행할 수 있을 것이다.

다음 세션

다음 세션은 2017년 8월 16일로 예정되어 있다. 이번이 두 번째 모임이기 때문에, 이 회의록이 React Native 커뮤니티에 어떻게 도움이 되는지 알고 싶다. 회의 결과물을 개선할 방법에 대한 제안이 있다면 트위터를 통해 언제든지 연락해 주시기 바란다.

React Native Monthly #1

· 11 min read
Tomislav Tenodi
Shoutem 제품 관리자

Shoutem에서는 React Native가 처음 등장했을 때부터 함께 작업할 기회를 얻었다. 우리는 이 놀라운 커뮤니티의 일원이 되기로 결심했다. 하지만 곧 커뮤니티가 성장하고 개선되는 속도를 따라잡는 것이 거의 불가능하다는 것을 깨달았다. 그래서 우리는 주요 React Native 기여자들이 자신들의 노력과 계획을 간단히 발표할 수 있는 월간 모임을 조직하기로 결정했다.

월간 회의

2017년 6월 14일, 첫 번째 월간 회의를 진행했다. React Native 월간 회의의 목표는 간단하고 명확하다: React Native 커뮤니티를 개선하는 것. 각 팀의 노력을 공유하면 오프라인에서 이루어지는 팀 간 협업이 더 원활해진다.

팀 소개

첫 번째 모임에는 8개 팀이 참여했다:

앞으로 더 많은 핵심 기여자들이 다음 세션에 참여하기를 기대한다!

공유 자료

팀의 계획이 더 많은 독자들에게 유용할 수 있다고 판단해, React Native 블로그를 통해 공유한다. 아래 내용을 확인해 보자:

Airbnb

  • ViewAccessibilityInfo 네이티브 모듈에 접근성(A11y) API를 추가할 계획이다.
  • 안드로이드 네이티브 모듈에 스레드를 지정해 실행할 수 있는 API를 추가하는 방안을 검토 중이다.
  • 초기화 성능 향상을 위한 가능성을 연구 중이다.
  • "unbundle" 위에 적용할 더 정교한 번들링 전략을 탐구 중이다.

콜스택

  • Detox를 E2E 테스트에 활용해 릴리스 프로세스를 개선하는 방안을 검토 중이다. 곧 풀 리퀘스트가 적용될 예정이다.
  • 작업 중이던 Blob 풀 리퀘스트가 머지되었고, 관련 후속 풀 리퀘스트가 곧 올라올 예정이다.
  • Haul을 내부 프로젝트에 점차 도입하며 Metro Bundler와의 성능 비교를 진행 중이다. 웹팩 팀과 협력해 멀티스레드 성능을 개선하는 작업도 진행 중이다.
  • 내부적으로 오픈소스 프로젝트를 관리하기 위한 더 나은 인프라를 구축했다. 앞으로 몇 주 안에 더 많은 내용을 공개할 계획이다.
  • React Native Europe 컨퍼런스가 다가오고 있지만, 아직 특별한 소식은 없다. 모두 초대한다!
  • 잠시 react-navigation에서 한 발 물러나 다른 네비게이션 솔루션(특히 네이티브 네비게이션)을 조사 중이다.

Expo

  • Snack에서 npm 모듈을 설치할 수 있도록 작업 중이다. 이 기능은 라이브러리 문서에 예제를 추가하는 데 유용할 것이다.
  • KrzysztofSoftware Mansion의 다른 사람들과 함께 Android에서 JSC 업데이트와 제스처 처리 라이브러리를 개발 중이다.
  • Adam Miskiewiczreact-navigation에 집중하기 위해 전환 중이다.
  • Create React Native App이 문서의 시작하기 가이드에 포함되었다. Expo는 라이브러리 작성자들이 자신들의 라이브러리가 CRNA와 호환되는지 명확히 설명하고, 호환된다면 설정 방법을 설명하도록 권장한다.

Facebook

  • React Native의 패키저가 이제 Metro Bundler로 독립된 리포지토리에서 관리된다. 런던에 위치한 Metro Bundler 팀은 커뮤니티의 요구를 해결하고, React Native 외의 다양한 사용 사례를 위한 모듈성을 개선하며, 이슈와 PR에 대한 응답성을 높이기 위해 노력하고 있다.
  • 앞으로 몇 달 동안 React Native 팀은 기본 컴포넌트의 API를 개선할 예정이다. 레이아웃 문제, 접근성, Flow 타이핑 등에서 개선이 이뤄질 것이다.
  • React Native 팀은 올해 코어 모듈성을 개선할 계획이다. Windows와 macOS와 같은 타사 플랫폼을 완벽히 지원하기 위해 리팩토링 작업을 진행할 것이다.

GeekyAnts

  • 팀은 .js 파일과 직접 연동되는 UI/UX 디자인 앱(코드명: Builder)을 개발 중이다. 현재는 React Native만 지원하며, Adobe XD나 Sketch와 유사한 기능을 제공한다.
  • 팀은 기존 React Native 앱을 에디터에서 불러와 디자이너가 시각적으로 수정한 뒤, 변경 사항을 바로 JS 파일에 저장할 수 있도록 열심히 작업 중이다.
  • 디자이너와 개발자 간의 간극을 줄이고, 두 팀이 동일한 레포지토리에서 협업할 수 있도록 노력하고 있다.
  • 또한 NativeBase가 최근 GitHub에서 5,000 스타를 달성했다.

마이크로소프트

  • CodePush가 이제 Mobile Center에 통합되었다. 이는 배포, 분석 및 기타 서비스와 더욱 통합된 환경을 제공하기 위한 첫 번째 단계다. 자세한 내용은 여기에서 확인할 수 있다.
  • VS Code에는 디버깅 관련 버그가 있으며, 현재 이를 수정 중이며 새로운 빌드를 출시할 예정이다.
  • 통합 테스트를 위해 Detox를 검토 중이며, JSC Context를 통해 크래시 리포트와 함께 변수를 확인하는 방법을 연구하고 있다.

Shoutem

  • 리액트 네이티브 커뮤니티의 도구를 활용해 Shoutem 앱 작업을 더 쉽게 만든다. Shoutem에서 생성한 앱을 실행하기 위해 모든 리액트 네이티브 커맨드를 사용할 수 있다.

  • 리액트 네이티브 프로파일링 도구를 연구 중이다. 설정 과정에서 많은 문제를 겪었고, 이를 해결하며 얻은 인사이트를 공유할 예정이다.

  • Shoutem은 기존 네이티브 앱과 리액트 네이티브를 더 쉽게 통합하는 방법을 개발 중이다. 회사 내부에서 구축한 개념을 문서화해 커뮤니티의 피드백을 받을 계획이다.

Wix

  • 내부적으로 Detox를 도입해 Wix 앱의 상당 부분을 "수동 QA 없이" 운영하는 방향으로 전환 중이다. 그 결과, 수십 명의 개발자가 프로덕션 환경에서 Detox를 활발히 사용하며 빠르게 성숙해지고 있다.
  • Metro Bundler에 빌드 중 파일 확장자를 오버라이드할 수 있는 기능을 추가 중이다. 기존의 "ios"와 "android"뿐만 아니라 "e2e"나 "detox"와 같은 커스텀 확장자도 지원할 예정이다. 이를 E2E 모킹에 활용할 계획이다. 이미 react-native-repackager라는 라이브러리가 있으며, 현재 PR 작업을 진행 중이다.
  • 성능 테스트 자동화를 연구 중이다. DetoxInstruments라는 새로운 저장소를 오픈소스로 개발 중이다. 관심 있다면 확인해 볼 수 있다.
  • KPN의 기여자와 함께 Detox의 Android 지원 및 실제 기기에서의 동작을 개선 중이다.
  • "Detox를 플랫폼으로 활용"해 시뮬레이터/기기를 자동화해야 하는 다른 도구를 구축할 수 있는 방안을 고민 중이다. 예를 들어 React Native용 Storybook이나 Ram의 통합 테스트 아이디어가 있다.

다음 모임

모임은 매 4주마다 진행된다. 다음 모임은 2017년 7월 12일로 예정되어 있다. 이번 모임을 시작으로, 여러분이 이 모임 기록이 React Native 커뮤니티에 어떻게 도움이 되는지 알고 싶다. 앞으로 다룰 주제나 모임 결과물을 개선할 방법에 대한 제안이 있다면 트위터를 통해 언제든지 연락해 주길 바란다.

Better List Views in React Native

· 11 min read
Spencer Ahrens
Software Engineer at Facebook

여러분 중 많은 분들이 이미 커뮤니티 그룹의 티저 발표를 보고 새로운 List 컴포넌트를 사용해 보기 시작했지만, 오늘 공식적으로 발표한다! 더 이상 ListViewDataSource, 오래된 행, 무시된 버그, 과도한 메모리 소비에 시달릴 필요가 없다. 최신 React Native 2017년 3월 릴리스 후보 버전(0.43-rc.1)에서 여러분은 새로운 컴포넌트 세트 중에서 가장 적합한 것을 선택할 수 있으며, 뛰어난 성능과 즉시 사용 가능한 기능들을 제공한다:

<FlatList>

이 컴포넌트는 간단하면서도 고성능 리스트를 구현할 때 주로 사용한다. 데이터 배열과 renderItem 함수만 제공하면 바로 사용할 수 있다:

<FlatList
data={[{title: 'Title Text', key: 'item1'}, ...]}
renderItem={({item}) => <ListItem title={item.title} />}
/>

<SectionList>

데이터를 논리적인 섹션으로 나누어 렌더링하고 싶다면, 예를 들어 알파벳 순 주소록처럼 섹션 헤더를 추가하거나, 프로필 뷰처럼 다양한 데이터와 렌더링 방식을 혼합하여 사용하고 싶다면 (예: 버튼, 컴포저, 사진 그리드, 친구 그리드, 스토리 리스트 순서로 구성된 화면), 이 컴포넌트를 사용하면 된다.

<SectionList
renderItem={({item}) => <ListItem title={item.title} />}
renderSectionHeader={({section}) => <H1 title={section.key} />}
sections={[ // 섹션 간 동일한 렌더링 방식
{data: [...], key: ...},
{data: [...], key: ...},
{data: [...], key: ...},
]}
/>

<SectionList
sections={[ // 섹션 간 다른 렌더링 방식
{data: [...], key: ..., renderItem: ...},
{data: [...], key: ..., renderItem: ...},
{data: [...], key: ..., renderItem: ...},
]}
/>

<VirtualizedList>

보다 유연한 API를 제공하는 내부 구현체다. 데이터가 일반 배열이 아닌 경우(예: 불변 리스트)에 특히 유용하다.

기능

리스트는 다양한 상황에서 활용되므로, 새로운 컴포넌트에는 대부분의 사용 사례를 즉시 처리할 수 있는 다양한 기능을 포함했다:

  • 스크롤 로딩 (onEndReached).
  • 당겨서 새로고침 (onRefresh / refreshing).
  • 구성 가능한 가시성(VPV) 콜백 (onViewableItemsChanged / viewabilityConfig).
  • 수평 모드 (horizontal).
  • 지능적인 아이템 및 섹션 구분자.
  • 다중 컬럼 지원 (numColumns).
  • scrollToEnd, scrollToIndex, 그리고 scrollToItem.
  • 향상된 Flow 타입 지정.

주의 사항

  • 아이템 서브트리의 내부 상태는 렌더링 윈도우를 벗어나 스크롤될 때 유지되지 않는다. 모든 데이터를 아이템 데이터나 Flux, Redux, Relay 같은 외부 저장소에 저장해야 한다.

  • 이 컴포넌트들은 PureComponent를 기반으로 동작한다. 따라서 props가 얕은 비교(shallow-equal)에서 동일하다면 리렌더링되지 않는다. renderItem 함수가 직접 의존하는 모든 값이 업데이트 후 === 비교에서 동일하지 않은 prop으로 전달되도록 해야 한다. 그렇지 않으면 UI가 변경 사항에 따라 업데이트되지 않을 수 있다. 이는 data prop과 부모 컴포넌트의 상태도 포함된다. 예를 들어:

    <FlatList
    data={this.state.data}
    renderItem={({item}) => (
    <MyItem
    item={item}
    onPress={() =>
    this.setState(oldState => ({
    selected: {
    // 새로운 인스턴스는 `===` 비교를 깨뜨림
    ...oldState.selected, // 기존 데이터 복사
    [item.key]: !oldState.selected[item.key], // 토글
    },
    }))
    }
    selected={
    !!this.state.selected[item.key] // renderItem은 state에 의존
    }
    />
    )}
    selected={
    // 기존 props와 충돌하지 않는 어떤 prop이라도 가능
    this.state.selected // selected가 변경되면 FlatList가 리렌더링됨
    }
    />
  • 메모리 사용을 제한하고 부드러운 스크롤을 가능하게 하기 위해, 콘텐츠는 오프스크린에서 비동기적으로 렌더링된다. 이는 스크롤 속도가 채우는 속도보다 빨라 잠깐 빈 콘텐츠가 보일 수 있음을 의미한다. 이는 각 애플리케이션의 필요에 따라 조정할 수 있는 트레이드오프이며, 내부적으로 이를 개선하기 위해 노력하고 있다.

  • 기본적으로 이 새로운 리스트는 각 아이템의 key prop을 찾아 React key로 사용한다. 또는 커스텀 keyExtractor prop을 제공할 수도 있다.

성능

새로운 리스트 컴포넌트는 API를 단순화할 뿐만 아니라, 성능 면에서도 상당한 개선을 이루었다. 가장 큰 장점은 행의 수와 상관없이 거의 일정한 메모리 사용량을 유지한다는 점이다. 이는 렌더링 윈도우 밖에 있는 엘리먼트를 '가상화'하여 컴포넌트 계층 구조에서 완전히 언마운트하고, React 컴포넌트의 JS 메모리와 섀도우 트리 및 UI 뷰의 네이티브 메모리를 회수함으로써 가능해졌다. 단, 이 경우 컴포넌트 내부 상태는 보존되지 않으므로, 중요한 상태는 컴포넌트 외부에서 관리해야 한다. 예를 들어 Relay, Redux, Flux 스토어 등을 활용할 수 있다.

렌더링 윈도우를 제한함으로써 React와 네이티브 플랫폼이 수행해야 하는 작업량도 줄어든다. 예를 들어 뷰 탐색 작업이 대표적이다. 수백만 개의 엘리먼트 중 마지막 항목을 렌더링하더라도, 새로운 리스트를 사용하면 모든 엘리먼트를 순회할 필요가 없다. scrollToIndex를 사용해 중간으로 바로 이동할 수 있으며, 이 과정에서 과도한 렌더링이 발생하지 않는다.

또한 스케줄링 측면에서도 개선이 이루어져 애플리케이션의 반응성이 향상되었다. 렌더링 윈도우의 가장자리에 위치한 항목은 적은 빈도로 렌더링되며, 활성 제스처나 애니메이션, 기타 상호작용이 완료된 후 낮은 우선순위로 처리된다.

고급 활용

ListView와 달리, 렌더링 윈도우 내의 모든 항목은 props가 변경될 때마다 리렌더링된다. 윈도우 기법이 항목 수를 일정하게 줄여주기 때문에 대부분의 경우 문제가 없지만, 항목이 복잡한 구조라면 성능을 위해 React의 모범 사례를 따르는 것이 좋다. React.PureComponent를 사용하거나 컴포넌트 내에서 shouldComponentUpdate를 적절히 활용해 재귀적 서브트리의 리렌더링을 제한할 수 있다.

항목을 렌더링하지 않고도 행의 높이를 계산할 수 있다면, getItemLayout prop을 제공해 사용자 경험을 개선할 수 있다. 이렇게 하면 scrollToIndex 등을 사용해 특정 항목으로 스크롤할 때 훨씬 부드러운 동작을 보장하며, 콘텐츠의 높이를 렌더링 없이도 결정할 수 있어 스크롤 표시기 UI도 향상된다.

불변 리스트(immutable list)와 같은 대체 데이터 타입을 사용한다면 <VirtualizedList>를 활용하는 것이 좋다. 이 컴포넌트는 getItem prop을 제공해 주어진 인덱스에 해당하는 항목 데이터를 반환할 수 있으며, 타입 체크도 좀 더 유연하게 처리한다.

특이한 사용 사례가 있다면 조정할 수 있는 다양한 파라미터도 있다. 예를 들어 windowSize를 사용해 메모리 사용량과 사용자 경험 사이의 균형을 조정하거나, maxToRenderPerBatch로 렌더링 속도와 반응성의 균형을 맞출 수 있다. onEndReachedThreshold를 사용해 스크롤 로딩이 발생하는 시점을 제어할 수도 있다.

향후 작업

  • 기존 컴포넌트의 마이그레이션 (최종적으로 ListView의 사용 중단).
  • 필요성에 따라 추가 기능 구현 (여러분의 의견을 보내주세요!).
  • 스틱키 고정 섹션 헤더 지원.
  • 더 많은 성능 최적화 작업.
  • 상태를 가진 함수형 아이템 컴포넌트 지원.

idx: The Existential Function

· 4 min read
Timothy Yung
Engineering Manager at Facebook

페이스북에서는 GraphQL로 가져온 데이터 구조에서 깊게 중첩된 값에 접근해야 하는 경우가 많다. 이러한 깊게 중첩된 값에 접근하는 과정에서 하나 이상의 중간 필드가 nullable인 경우가 흔하다. 중간 필드가 null이 될 수 있는 이유는 다양하다. 실패한 개인정보 확인부터, 치명적이지 않은 오류를 표현하는 가장 유연한 방법으로 null을 사용하는 경우까지 여러 가지가 있다.

안타깝게도, 깊게 중첩된 값에 접근하는 작업은 현재 매우 번거롭고 장황하다.

props.user &&
props.user.friends &&
props.user.friends[0] &&
props.user.friends[0].friends;

ECMAScript에 존재 여부를 확인하는 연산자를 도입하는 제안이 있어 이를 훨씬 편리하게 만들 수 있다. 하지만 이 제안이 확정되기 전까지, 우리는 삶의 질을 개선하고 기존 언어의 의미를 유지하며 Flow를 통한 타입 안전성을 장려하는 해결책을 원했다.

그래서 우리는 idx라는 존재 여부를 확인하는 _함수_를 만들었다.

idx(props, _ => _.user.friends[0].friends);

이 코드 스니펫의 호출은 위의 불리언 표현식과 유사하게 동작하지만, 반복이 훨씬 적다. idx 함수는 정확히 두 개의 인자를 받는다:

  • 일반적으로 중첩된 값에 접근하려는 객체나 배열 등의 값.
  • 첫 번째 인자를 받아 그 위에서 중첩된 값에 접근하는 함수.

이론적으로, idx 함수는 null이나 undefined에 접근할 때 발생하는 오류를 try-catch로 잡으려고 시도한다. 이런 오류가 잡히면 null이나 undefined를 반환한다. (이 구현 방법은 idx.js에서 확인할 수 있다.)

실제로, 모든 중첩된 속성 접근을 try-catch로 감싸는 것은 느리고, 특정 종류의 TypeError를 구분하는 것은 취약하다. 이러한 단점을 해결하기 위해, 우리는 위의 idx 호출을 다음 표현식으로 변환하는 Babel 플러그인을 만들었다:

props.user == null
? props.user
: props.user.friends == null
? props.user.friends
: props.user.friends[0] == null
? props.user.friends[0]
: props.user.friends[0].friends;

마지막으로, 우리는 idx에 대한 커스텀 Flow 타입 선언을 추가했다. 이를 통해 두 번째 인자에서의 탐색이 적절히 타입 체크되면서도 nullable 속성에 대한 중첩 접근이 허용된다.

이 함수, Babel 플러그인, 그리고 Flow 선언은 이제 GitHub에서 사용할 수 있다. idxbabel-plugin-idx npm 패키지를 설치하고 .babelrc 파일의 플러그인 목록에 "idx"를 추가하면 사용할 수 있다.

Introducing Create React Native App

· 4 min read
Adam Perry
Software Engineer at Expo

오늘 우리는 Create React Native App을 발표한다. 이 새로운 도구는 React Native 프로젝트를 시작하는 과정을 크게 단순화한다. 이 도구는 Create React App의 디자인에서 큰 영감을 받았으며, FacebookExpo(이전의 Exponent)의 협력으로 만들어졌다.

많은 개발자들이 특히 Android를 위해 React Native의 네이티브 빌드 의존성을 설치하고 설정하는 데 어려움을 겪는다. Create React Native App을 사용하면 Xcode나 Android Studio를 사용할 필요가 없으며, Linux나 Windows에서도 iOS 기기를 위한 개발이 가능하다. 이는 Expo 앱을 통해 이루어진다. Expo 앱은 순수 JavaScript로 작성된 CRNA 프로젝트를 네이티브 코드를 컴파일하지 않고도 로드하고 실행한다.

새로운 프로젝트를 생성해 보자(yarn이 설치되어 있다면 적절한 yarn 명령어로 대체 가능):

$ npm i -g create-react-native-app
$ create-react-native-app my-project
$ cd my-project
$ npm start

이 명령어는 React Native 패키저를 시작하고 QR 코드를 출력한다. Expo 앱에서 이 QR 코드를 열어 JavaScript를 로드할 수 있다. console.log 호출은 터미널로 전달된다. 모든 표준 React Native API와 Expo SDK를 사용할 수 있다.

네이티브 코드는 어떻게 처리할까?

많은 React Native 프로젝트는 컴파일이 필요한 Java 또는 Objective-C/Swift 의존성을 포함한다. Expo 앱은 카메라, 비디오, 연락처 등의 API를 제공하며, Airbnb의 react-native-maps나 Facebook 인증과 같은 인기 라이브러리도 포함한다. 하지만 Expo가 제공하지 않는 네이티브 코드 의존성이 필요한 경우, 직접 빌드 환경을 구성해야 한다. Create React App과 마찬가지로 CRNA에서도 "이젝트(eject)" 기능을 지원한다.

npm run eject 명령어를 실행하면 react-native init으로 생성한 프로젝트와 유사한 구조를 얻을 수 있다. 이 시점부터는 react-native init으로 시작한 경우와 마찬가지로 Xcode나 Android Studio가 필요하다. react-native link로 라이브러리를 추가할 수 있으며, 네이티브 코드 컴파일 과정을 완전히 제어할 수 있다.

궁금한 점이나 피드백이 있나요?

Create React Native App은 이제 일반 사용에 충분히 안정화되었습니다. 여러분의 사용 경험을 듣고 싶습니다! 트위터를 통해 연락하거나 GitHub 저장소에 이슈를 열어주세요. 풀 리퀘스트도 환영합니다!

Using Native Driver for Animated

· 12 min read
Janic Duplessis
Software Engineer at App & Flow

지난 1년 동안, 우리는 Animated 라이브러리를 사용한 애니메이션의 성능을 개선하기 위해 노력해 왔다. 애니메이션은 아름다운 사용자 경험을 만드는 데 매우 중요하지만, 올바르게 구현하기 쉽지 않다. 우리는 개발자가 성능 저하를 일으킬 수 있는 코드를 걱정하지 않고도 효율적인 애니메이션을 쉽게 만들 수 있도록 하고 싶다.

이 기능의 목적

Animated API는 매우 중요한 제약 조건을 염두에 두고 설계되었다. 바로 직렬화 가능하다는 점이다. 이는 애니메이션이 시작되기 전에 모든 정보를 네이티브로 보낼 수 있음을 의미한다. 따라서 네이티브 코드가 UI 스레드에서 애니메이션을 수행할 때, 매 프레임마다 브리지를 거칠 필요가 없다. 이 기능은 매우 유용한데, 애니메이션이 시작된 후에도 JS 스레드가 블로킹되더라도 애니메이션이 부드럽게 실행될 수 있기 때문이다. 실제로 사용자 코드가 JS 스레드에서 실행되고 React 렌더링이 JS를 오랫동안 잠글 수 있기 때문에 이러한 상황은 자주 발생할 수 있다.

약간의 역사...

이 프로젝트는 약 1년 전 Expo가 Android용 li.st 앱을 개발하면서 시작되었다. Krzysztof Magiera는 Android에서 초기 구현을 담당했다. 결과는 성공적이었고, li.st는 Animated를 사용해 네이티브로 구동되는 애니메이션을 탑재한 첫 번째 앱이 되었다. 몇 달 후, Brandon Withrow가 iOS에서 초기 구현을 완성했다. 이후 Ryan Gomba와 나는 Animated.event 지원 같은 누락된 기능을 추가하고, 실제 프로덕션 앱에서 발견한 버그를 수정하는 작업을 진행했다. 이는 진정한 커뮤니티의 노력이었으며, 관련된 모든 분들과 개발의 상당 부분을 후원한 Expo에 감사드린다. 이제 이 기술은 React Native의 Touchable 컴포넌트와 최근 출시된 React Navigation 라이브러리의 네비게이션 애니메이션에서 사용되고 있다.

원리 이해

먼저, JS 드라이버를 사용하는 Animated로 애니메이션이 어떻게 동작하는지 살펴보자. Animated를 사용할 때는 수행하려는 애니메이션을 나타내는 노드 그래프를 선언한 후, 드라이버를 사용해 미리 정의된 곡선에 따라 Animated 값을 업데이트한다. 또한 Animated.event를 사용해 View의 이벤트에 Animated 값을 연결해 업데이트할 수도 있다.

애니메이션의 단계와 각 단계가 어디서 일어나는지 정리하면 다음과 같다:

  • JS: 애니메이션 드라이버는 requestAnimationFrame을 사용해 매 프레임마다 실행되고, 애니메이션 곡선을 기반으로 계산한 새로운 값으로 드라이브되는 값을 업데이트한다.
  • JS: 중간 값이 계산되어 View에 연결된 props 노드로 전달된다.
  • JS: setNativeProps를 사용해 View가 업데이트된다.
  • JS에서 Native로 브리지를 거친다.
  • Native: UIView 또는 android.View가 업데이트된다.

보이는 것처럼 대부분의 작업은 JS 스레드에서 일어난다. JS 스레드가 블로킹되면 애니메이션이 프레임을 건너뛰게 된다. 또한 매 프레임마다 JS에서 Native로 브리지를 거쳐 네이티브 뷰를 업데이트해야 한다.

네이티브 드라이버는 이 모든 단계를 네이티브로 옮긴다. Animated가 애니메이션 노드 그래프를 생성하기 때문에, 애니메이션이 시작될 때 한 번만 네이티브로 직렬화하여 전송할 수 있다. 이렇게 하면 JS 스레드로 다시 콜백할 필요가 없어지고, 네이티브 코드가 매 프레임마다 UI 스레드에서 직접 뷰를 업데이트할 수 있다.

애니메이션 값과 보간 노드를 직렬화하는 예제를 살펴보자 (정확한 구현은 아니고 예제일 뿐이다).

네이티브 값 노드를 생성한다. 이 값이 애니메이션될 값이다:

NativeAnimatedModule.createNode({
id: 1,
type: 'value',
initialValue: 0,
});

네이티브 보간 노드를 생성한다. 이 노드는 네이티브 드라이버에게 값을 어떻게 보간할지 알려준다:

NativeAnimatedModule.createNode({
id: 2,
type: 'interpolation',
inputRange: [0, 10],
outputRange: [10, 0],
extrapolate: 'clamp',
});

네이티브 props 노드를 생성한다. 이 노드는 네이티브 드라이버에게 뷰의 어떤 prop에 연결되었는지 알려준다:

NativeAnimatedModule.createNode({
id: 3,
type: 'props',
properties: ['style.opacity'],
});

노드들을 연결한다:

NativeAnimatedModule.connectNodes(1, 2);
NativeAnimatedModule.connectNodes(2, 3);

props 노드를 뷰에 연결한다:

NativeAnimatedModule.connectToView(3, ReactNative.findNodeHandle(viewRef));

이렇게 하면 네이티브 애니메이션 모듈은 JS로 값을 계산할 필요 없이 네이티브 뷰를 직접 업데이트하는 데 필요한 모든 정보를 갖게 된다.

남은 일은 어떤 타입의 애니메이션 곡선을 사용할지와 어떤 애니메이션 값을 업데이트할지 지정해 애니메이션을 실제로 시작하는 것이다. 타이밍 애니메이션은 JS에서 미리 모든 프레임을 계산해 네이티브 구현을 더 작게 만들 수 있다.

NativeAnimatedModule.startAnimation({
type: 'timing',
frames: [0, 0.1, 0.2, 0.4, 0.65, ...],
animatedValueId: 1,
});

이제 애니메이션이 실행될 때 일어나는 일을 정리하면 다음과 같다:

  • Native: 네이티브 애니메이션 드라이버는 CADisplayLink 또는 android.view.Choreographer를 사용해 매 프레임마다 실행되고, 애니메이션 곡선을 기반으로 계산한 새로운 값으로 드라이브되는 값을 업데이트한다.
  • Native: 중간 값이 계산되어 네이티브 뷰에 연결된 props 노드로 전달된다.
  • Native: UIView 또는 android.View가 업데이트된다.

보이는 것처럼 더 이상 JS 스레드와 브리지가 필요 없으므로 애니메이션이 더 빨라진다! 🎉🎉

앱에서 사용하는 방법

일반적인 애니메이션의 경우 답은 간단하다. 애니메이션을 시작할 때 설정에 useNativeDriver: true를 추가하면 된다.

이전 코드:

Animated.timing(this.state.animatedValue, {
toValue: 1,
duration: 500,
}).start();

변경 후 코드:

Animated.timing(this.state.animatedValue, {
toValue: 1,
duration: 500,
useNativeDriver: true, // <-- 이 부분 추가
}).start();

애니메이션 값은 하나의 드라이버와만 호환된다. 따라서 어떤 값에 대해 애니메이션을 시작할 때 네이티브 드라이버를 사용한다면, 해당 값에 대한 모든 애니메이션도 네이티브 드라이버를 사용해야 한다.

이것은 Animated.event에서도 동작한다. 스크롤 위치를 따라야 하는 애니메이션에 매우 유용하다. 네이티브 드라이버를 사용하지 않으면 React Native의 비동기적 특성 때문에 제스처보다 항상 한 프레임 뒤처지게 된다.

이전 코드:

<ScrollView
scrollEventThrottle={16}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: this.state.animatedValue } } }]
)}
>
{content}
</ScrollView>

변경 후 코드:

<Animated.ScrollView // <-- Animated ScrollView 래퍼 사용
scrollEventThrottle={1} // <-- 이벤트가 누락되지 않도록 1로 설정
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: this.state.animatedValue } } }],
{ useNativeDriver: true } // <-- 이 부분 추가
)}
>
{content}
</Animated.ScrollView>

주의사항

Native Animated에서 Animated의 모든 기능을 사용할 수 있는 것은 아니다. 주요 제한 사항은 레이아웃 속성이 아닌 프로퍼티만 애니메이션으로 처리할 수 있다는 점이다. 예를 들어 transformopacity는 작동하지만, 플렉스 박스와 위치 관련 속성은 지원하지 않는다. 또 다른 제한은 Animated.event인데, 이벤트 버블링이 아닌 직접 이벤트에서만 작동한다. 즉, PanResponder와는 호환되지 않지만 ScrollView#onScroll 같은 이벤트에서는 정상적으로 동작한다.

Native Animated는 React Native에 오랫동안 포함되어 있었지만, 실험적인 기능으로 간주되어 문서화되지 않았다. 따라서 이 기능을 사용하려면 React Native의 최신 버전(0.40 이상)을 사용해야 한다.

리소스

애니메이션에 대해 더 자세히 알고 싶다면 Christopher Chedeau이 강연을 추천한다.

애니메이션을 네이티브로 오프로딩하면 사용자 경험이 어떻게 개선되는지 깊이 있게 알고 싶다면 Krzysztof Magiera이 강연도 참고하면 좋다.

월간 릴리스 주기: 12월과 1월 RC 출시

· 4 min read
Eric Vicenti
Facebook 엔지니어

React Native가 처음 소개된 직후, 우리는 커뮤니티가 새로운 기능을 빠르게 도입할 수 있도록 매주 2주마다 릴리즈를 시작했다. 동시에 프로덕션 환경에서 사용할 수 있도록 버전을 안정적으로 유지했다. Facebook 내부에서는 프로덕션 iOS 앱을 출시하기 위해 매주 2주마다 코드베이스를 안정화해야 했기 때문에, 오픈소스 버전도 동일한 속도로 릴리즈하기로 결정했다. 현재 Facebook의 많은 앱들은 주 단위로 출시되며, 특히 Android에서 더욱 그렇다. 우리는 주 단위로 master 브랜치에서 출시하기 때문에, 이를 매우 안정적으로 유지해야 한다. 따라서 2주마다 릴리즈하는 방식은 더 이상 내부 기여자들에게도 이점이 없다.

커뮤니티에서는 릴리즈 속도가 따라가기 어렵다는 피드백을 자주 받는다. Expo와 같은 도구들은 버전 변경 속도를 관리하기 위해 매번 릴리즈를 건너뛰어야 했다. 따라서 2주마다 릴리즈하는 방식이 커뮤니티에 잘 맞지 않는다는 것이 분명해 보인다.

이제 매월 정기 릴리스 진행

매월 정기 릴리스를 진행한다는 소식을 기쁜 마음으로 전한다. 2016년 12월 릴리스인 v0.40은 지난 한 달 동안 안정화 작업을 거쳤으며, 이제 도입할 준비가 됐다. (단, iOS 네이티브 모듈에서 헤더를 업데이트해야 한다는 점을 잊지 말자).

주말을 피하거나 예상치 못한 이슈를 처리하기 위해 며칠 정도 차이가 날 수 있지만, 이제 매월 첫째 날에 릴리스를 사용할 수 있고, 마지막 날에 공개될 것으로 기대해도 된다.

최신 릴리즈 후보 버전 사용 권장

1월 릴리즈 후보 버전을 지금 바로 사용해 볼 수 있습니다. 새로운 기능을 여기서 확인하세요.

변경 사항을 미리 확인하고 React Native 기여자들에게 더 나은 피드백을 제공하려면, 가능한 한 매월 최신 릴리즈 후보 버전을 사용하는 것이 좋습니다. 매월 말에 정식 버전이 출시될 때쯤이면, 해당 버전에 포함된 변경 사항이 페이스북 앱에서 이미 2주 이상 운영 환경에서 테스트된 상태입니다.

새로운 react-native-git-upgrade 커맨드를 사용하면 앱을 쉽게 업그레이드할 수 있습니다:

npm install -g react-native-git-upgrade
react-native-git-upgrade 0.41.0-rc.0

이 간편한 접근 방식이 React Native 커뮤니티가 변경 사항을 더 쉽게 추적하고, 새로운 버전을 빠르게 적용하는 데 도움이 되길 바랍니다!

(이 계획을 고안한 Martin Konicek과 이를 실현한 Mike Grabowski에게 감사드립니다)

Git을 통한 더 쉬운 업그레이드

· 8 min read
Nicolas Cuillery
Zenika에서 JavaScript 컨설턴트 및 트레이너

React Native의 새 버전으로 업그레이드하는 것은 항상 어려운 일이었다. 다음과 같은 상황을 경험해 본 적이 있을 것이다:

이러한 옵션 중 어느 것도 이상적이지 않다. 파일을 덮어쓰면 로컬 변경사항을 잃게 된다. 덮어쓰지 않으면 최신 업데이트를 적용할 수 없다.

오늘 이 문제를 해결할 수 있는 새로운 도구를 소개하게 되어 기쁘다. 이 도구는 react-native-git-upgrade라고 하며, 가능한 경우 Git을 사용해 자동으로 충돌을 해결한다.

사용법

필수 조건: Git이 PATH에 설치되어 있어야 한다. 프로젝트가 Git으로 관리되지 않아도 된다.

react-native-git-upgrade를 전역으로 설치한다:

$ npm install -g react-native-git-upgrade

또는 Yarn을 사용한다:

$ yarn global add react-native-git-upgrade

그런 다음, 프로젝트 디렉터리 안에서 실행한다:

$ cd MyProject
$ react-native-git-upgrade 0.38.0

참고: react-native의 새 버전을 설치하기 위해 'npm install'을 실행하지 않는다. 이 도구는 이전과 새로운 프로젝트 템플릿을 비교할 수 있어야 정상적으로 작동한다. 위에서 보여준 대로 앱 폴더 안에서 실행하면 된다. 이때 아직 이전 버전에 머물러 있어야 한다.

예제 출력:

react-native-git-upgrade를 인수 없이 실행하면 React Native의 최신 버전으로 업그레이드한다.

Android와 iOS 빌드 파일에서 변경 사항을 보존하려고 노력하므로, 업그레이드 후 react-native link를 실행할 필요가 없다.

구현을 최대한 간섭하지 않도록 설계했다. 임시 디렉터리에 생성된 로컬 Git 저장소를 기반으로 작동한다. 프로젝트 저장소에 영향을 미치지 않는다 (Git, SVN, Mercurial 등 어떤 VCS를 사용하든 상관없다). 예기치 않은 오류가 발생하면 소스가 복원된다.

어떻게 동작하나요?

핵심 단계는 Git 패치를 생성하는 것이다. 이 패치는 앱이 사용 중인 버전과 새로운 버전 사이의 React Native 템플릿에 적용된 모든 변경 사항을 포함한다.

이 패치를 얻으려면 node_modules 디렉토리 내부의 react-native 패키지에 포함된 템플릿으로 앱을 생성해야 한다. 이 템플릿은 react-native init 명령어가 사용하는 것과 동일하다. 그런 다음 현재 버전과 새 버전의 템플릿으로 각각 네이티브 앱을 생성한 후, Git은 프로젝트에 맞게 조정된 패치를 생성할 수 있다. 이 패치는 앱 이름과 같은 프로젝트별 정보를 포함한다.

[...]

diff --git a/ios/MyAwesomeApp/Info.plist b/ios/MyAwesomeApp/Info.plist
index e98ebb0..2fb6a11 100644
--- a/ios/MyAwesomeApp/Info.plist
+++ b/ios/MyAwesomeApp/Info.plist
@@ -45,7 +45,7 @@
<dict>
<key>localhost</key>
<dict>
- <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
+ <key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
[...]

이제 이 패치를 소스 파일에 적용하기만 하면 된다. 이전 react-native upgrade 프로세스는 작은 차이점도 사용자에게 물어보곤 했지만, Git은 3-way 병합 알고리즘을 사용해 대부분의 변경 사항을 자동으로 병합할 수 있다. 결국 익숙한 충돌 구분자를 남긴다.

    13B07F951A680F5B00A75B9A /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
<<<<<<< ours
CODE_SIGN_IDENTITY = "iPhone Developer";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/HockeySDK.embeddedframework",
"$(PROJECT_DIR)/HockeySDK-iOS/HockeySDK.embeddedframework",
);
=======
CURRENT_PROJECT_VERSION = 1;
>>>>>>> theirs
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../node_modules/react-native/React/**",
"$(SRCROOT)/../node_modules/react-native-code-push/ios/CodePush/**",
);

이러한 충돌은 일반적으로 이해하기 쉽다. 구분자 ours는 "여러분의 팀"을 의미하고, theirs는 "React Native 팀"으로 볼 수 있다.

새로운 글로벌 패키지를 도입하는 이유

React Native는 기본적으로 전역 CLI(react-native-cli 패키지)를 제공한다. 이 CLI는 node_modules/react-native/local-cli 디렉토리에 내장된 로컬 CLI로 명령을 위임한다.

앞서 언급했듯이, 이 프로세스는 현재 사용 중인 React Native 버전에서 시작해야 한다. 만약 이 기능을 로컬 CLI에 내장했다면, 이전 버전의 React Native를 사용할 때 이 기능을 활용할 수 없게 된다. 예를 들어, 0.29.2 버전에서 0.38.0 버전으로 업그레이드하려면 이 새로운 업그레이드 코드가 0.38.0 버전에서만 출시되었다면 업그레이드가 불가능하다.

Git 기반의 업그레이드는 개발자 경험을 크게 개선하며, 모든 사람이 이를 사용할 수 있도록 하는 것이 중요하다. 전역으로 설치되는 별도의 패키지인 react-native-git-upgrade를 사용하면 현재 프로젝트가 어떤 React Native 버전을 사용하든 상관없이 이 새로운 코드를 바로 활용할 수 있다.

또 다른 이유는 Martin Konicek의 최근 Yeoman 제거이다. 패치를 생성하기 위해 이전 템플릿을 평가하려면 react-native 패키지에 Yeoman 의존성을 다시 추가해야 하는데, 이를 원하지 않았다.

직접 사용해 보고 피드백을 제공하세요

결론적으로, 이 기능을 즐기고 개선 사항을 제안하거나 문제를 보고하고 특히 풀 리퀘스트를 보내주세요. 각 환경은 조금씩 다르고, 각 React Native 프로젝트도 다릅니다. 여러분의 피드백이 필요하니, 이 기능이 모두에게 잘 동작할 수 있도록 도와주세요.

감사의 말

이 모든 것이 가능하도록 도와주신 멋진 회사 ZenikaM6 Web (archived)에게 깊은 감사를 드립니다!

Introducing Button, Faster Installs with Yarn, and a Public Roadmap

· 5 min read
Héctor Ramos
Héctor Ramos
Former Developer Advocate at Facebook

많은 사람들이 React Native에서 활발히 진행 중인 작업을 따라잡기 어렵다고 이야기한다. 진행 중인 작업을 명확히 전달하기 위해, 이제 React Native 로드맵을 공개한다. 이 작업은 크게 세 가지 우선순위로 나뉜다:

  • 코어 라이브러리. 가장 유용한 컴포넌트와 API에 더 많은 기능을 추가한다.
  • 안정성. 기반 인프라를 개선해 버그를 줄이고 코드 품질을 높인다.
  • 개발자 경험. React Native 개발자가 더 빠르게 작업할 수 있도록 돕는다.

로드맵에 추가할 만한 기능을 제안하고 싶다면 Canny를 확인해 보자. 여기서 새로운 기능을 제안하거나 기존 제안에 대해 논의할 수 있다.

React Native의 새로운 기능

React Native 버전 0.37이 오늘 출시되며, 앱에 터치 가능한 버튼을 쉽게 추가할 수 있는 새로운 코어 컴포넌트가 도입되었다. 또한 새로운 패키지 관리자인 Yarn을 지원하여 앱의 의존성을 업데이트하는 전체 프로세스가 더 빨라질 것으로 기대된다.

버튼 컴포넌트 소개

오늘 우리는 모든 플랫폼에서 훌륭하게 보이는 기본 <Button /> 컴포넌트를 소개한다. 이는 React Native가 즉시 사용할 수 있는 버튼을 제공하지 않는 유일한 모바일 개발 도구 중 하나라는 피드백에 대한 해결책이다.

안드로이드와 iOS에서의 간단한 버튼

<Button
onPress={onPressMe}
title="Press Me"
accessibilityLabel="Learn more about this Simple Button"
/>

경험 많은 React Native 개발자들은 버튼을 만드는 방법을 알고 있다. iOS에서는 TouchableOpacity를, 안드로이드에서는 물결 효과를 위해 TouchableNativeFeedback을 사용한 뒤 몇 가지 스타일을 적용한다. 커스텀 버튼을 만들거나 설치하는 것이 특별히 어렵지는 않지만, 우리는 React Native를 배우기 쉽게 만드는 것을 목표로 한다. 코어에 기본 버튼을 추가함으로써, 초보자들은 첫날부터 멋진 것을 개발할 수 있게 되며 버튼 서식을 맞추거나 Touchable의 세부 사항을 배우는 데 시간을 쏟지 않아도 된다.

버튼은 모든 플랫폼에서 자연스럽게 작동하고 보이도록 설계되었기 때문에, 커스텀 버튼이 제공하는 모든 기능을 지원하지는 않는다. 이는 훌륭한 시작점이지만, 기존의 모든 버튼을 대체하기 위한 것은 아니다. 더 자세한 내용은 실행 가능한 예제가 포함된 새로운 버튼 문서를 확인하라!

Yarn을 사용해 react-native init 속도 높이기

이제 자바스크립트의 새로운 패키지 매니저인 Yarn을 사용해 react-native init의 속도를 크게 높일 수 있다. 속도 향상을 확인하려면 Yarn을 설치하고 react-native-cli를 1.2.0 버전으로 업그레이드한다:

$ npm install -g react-native-cli

이제 새 앱을 설정할 때 "Using yarn" 메시지를 확인할 수 있다:

Using yarn

간단한 로컬 테스트에서 react-native init좋은 네트워크 환경에서 약 1분 내에 완료된다(npm 3.10.8을 사용할 때 약 3분 소요). Yarn 설치가 필수는 아니지만 강력히 권장한다.

감사합니다!

이번 릴리스에 기여해 주신 모든 분들께 감사드립니다. 전체 릴리스 노트는 GitHub에서 확인할 수 있습니다. 수십 개의 버그 수정과 새로운 기능이 추가되면서 React Native는 여러분 덕분에 계속해서 발전하고 있습니다.