React Native에서는 이미 두 애니메이션 값을 더하거나, 곱하거나, 모듈로 연산하는 기능을 지원한다. 버전 0.36부터는 두 애니메이션 값을 나누는 기능도 추가되었다. 특정 경우에는 계산을 위해 한 애니메이션 값을 다른 애니메이션 값으로 역수로 만들어야 할 때가 있다. 예를 들어 스케일을 역수로 만드는 경우가 있다 (2x --> 0.5x):
const a =Animated.Value(1); const b =Animated.divide(1, a); Animated.spring(a,{ toValue:2, }).start();
css-layout은 이미 레이아웃을 위해 start와 end 개념을 도입했다. Left-to-Right(LTR) 레이아웃에서 start는 left를 의미하고, end는 right를 의미한다. 하지만 RTL 레이아웃에서는 start가 right를, end가 left를 의미한다. 이를 통해 RN은 start와 end 계산에 의존해 position, padding, margin을 포함한 올바른 레이아웃을 계산할 수 있다.
또한 css-layout은 각 컴포넌트의 방향을 부모로부터 상속받도록 설계했다. 따라서 루트 컴포넌트의 방향을 RTL로 설정하면 전체 앱이 뒤집힌다.
안드로이드와 iOS 개발에서 RTL 레이아웃으로 변경하면, 제스처와 애니메이션이 LTR 레이아웃과 반대 방향으로 동작한다. 현재 리액트 네이티브(RN)에서는 제스처와 애니메이션이 RN 코어 코드 수준에서 지원되지 않고, 컴포넌트 수준에서 지원된다. 다행히 일부 컴포넌트는 이미 RTL을 지원하고 있으며, 예를 들어 SwipeableRow와 NavigationExperimental가 있다. 그러나 제스처를 사용하는 다른 컴포넌트들은 수동으로 RTL을 지원해야 한다.
초기 RTL 호환 앱 출시 이후에도 새로운 기능을 반복적으로 추가해야 할 경우가 많다. 개발 효율성을 높이기 위해 I18nManager는 테스트 기기의 언어 설정을 변경하지 않고도 빠르게 RTL을 테스트할 수 있는 forceRTL() 함수를 제공한다. 앱 내에 이 기능을 간단히 전환할 수 있는 스위치를 추가하는 것도 좋은 방법이다. 다음은 RNTester의 RTL 예제에서 가져온 코드다:
<RNTesterBlock title={'Quickly Test RTL Layout'}> <View style={styles.flexDirectionRow}> <Text style={styles.switchRowTextView}>forceRTL</Text> <View style={styles.switchRowSwitchView}> <Switch onValueChange={this._onDirectionChange} style={styles.rightAlignStyle} value={this.state.isRTL} /> </View> </View> </RNTesterBlock>; _onDirectionChange=()=>{ I18nManager.forceRTL(!this.state.isRTL); this.setState({isRTL:!this.state.isRTL}); Alert.alert( 'Reload this page', 'Please reload this page to change the UI direction! '+ 'All examples in this app will be affected. '+ 'Check them out to see what they look like in RTL layout.', ); };
새로운 기능을 개발할 때 이 버튼을 쉽게 토글하고 앱을 리로드해 RTL 레이아웃을 확인할 수 있다. 이 방법의 장점은 언어 설정을 변경하지 않고도 테스트가 가능하다는 점이다. 하지만 다음 섹션에서 설명하듯 일부 텍스트 정렬은 변경되지 않을 수 있다. 따라서 앱 출시 전에 반드시 RTL 언어로 테스트하는 것이 좋다.
RTL(오른쪽에서 왼쪽) 지원은 앱의 대부분의 사용자 경험을 커버해야 한다. 하지만 현재 몇 가지 제한 사항이 존재한다:
텍스트 정렬 방식이 Android와 iOS에서 다르게 동작한다.
iOS에서는 기본 텍스트 정렬이 활성 언어 번들에 따라 결정되며, 항상 한쪽으로 고정된다. Android에서는 텍스트 콘텐츠의 언어에 따라 달라진다. 예를 들어, 영어는 왼쪽 정렬, 아랍어는 오른쪽 정렬된다.
이론적으로는 플랫폼 간 일관성을 유지해야 하지만, 사용자가 앱을 사용할 때 한 동작을 다른 동작보다 선호할 수 있다. 텍스트 정렬에 대한 최적의 방식을 찾기 위해 더 많은 사용자 경험 연구가 필요할 수 있다.
"진짜" 왼쪽/오른쪽 개념이 없다.
앞서 논의한 것처럼, JS 측면에서 left/right 스타일을 start/end로 매핑한다. RTL 레이아웃에서 코드의 모든 left는 화면에서 "오른쪽"이 되고, 코드의 right는 화면에서 "왼쪽"이 된다. 이는 제품 코드를 크게 변경할 필요가 없어 편리하지만, 코드에서 "진짜 왼쪽"이나 "진짜 오른쪽"을 지정할 방법이 없다는 의미이다. 향후에는 언어와 무관하게 컴포넌트가 방향을 제어할 수 있도록 하는 것이 필요할 수 있다.
제스처와 애니메이션에 대한 RTL 지원을 개발자 친화적으로 개선한다.
현재 제스처와 애니메이션이 RTL과 호환되도록 하려면 여전히 프로그래밍 노력이 필요하다. 앞으로는 제스처와 애니메이션의 RTL 지원을 더 개발자 친화적으로 만드는 방법을 찾는 것이 이상적일 것이다.
첫 번째 소식에 이어 저녁 행사의 주최사인 Zynga가 간단한 소개를 진행했다. 아비쉐크 차다는 모바일에서 새로운 경험을 빠르게 프로토타이핑하기 위해 React를 어떻게 사용하는지 설명했다. Draw Something와 유사한 앱의 빠른 프로토타입을 시연했다. 그들은 React Native와 유사한 접근 방식을 사용해 브리지를 통해 네이티브 API에 접근한다. 아비쉐크가 기기의 카메라를 사용해 관객의 사진을 찍은 다음, 누군가의 머리에 모자를 그리는 것으로 이를 시연했다.
다음은 오늘 밤 첫 번째 주요 발표다. Netflix의 수석 소프트웨어 엔지니어 Clarence Leung이 React Native를 위한 API 설계에 대해 발표했다. 그는 먼저 작업할 수 있는 두 가지 주요 라이브러리 타입을 언급했다. 탭 바와 날짜 선택기 같은 컴포넌트와 카메라 롤이나 인앱 결제 같은 네이티브 서비스에 접근을 제공하는 라이브러리다. React Native에서 사용할 라이브러리를 구축할 때 두 가지 접근 방식이 있다:
플랫폼별로 특화된 컴포넌트 제공
Android와 iOS 모두에 대해 유사한 API를 가진 크로스 플랫폼 라이브러리
각 접근 방식에는 고려할 사항이 있으며, 어떤 방식이 필요에 가장 적합한지는 독자가 결정해야 한다.
접근 방식 #1
플랫폼별로 특화된 컴포넌트의 예로 Clarence는 React Native 코어의 DatePickerIOS와 DatePickerAndroid를 언급했다. iOS에서는 날짜 선택기가 UI의 일부로 렌더링되며 기존 뷰에 쉽게 포함할 수 있지만, Android에서는 모달로 표시된다. 이런 경우에는 별도의 컴포넌트를 제공하는 것이 합리적이다.
접근 방식 #2
반면, 사진 선택기는 Android와 iOS에서 비슷하게 처리된다. Android는 iOS의 Selfies처럼 사진을 폴더로 그룹화하지 않는 등 약간의 차이가 있지만, if 문과 Platform 컴포넌트를 사용해 쉽게 처리할 수 있다.
어떤 접근 방식을 선택하든, API 표면을 최소화하고 앱 특화 라이브러리를 구축하는 것이 좋다. 예를 들어, iOS의 인앱 구매 프레임워크는 일회성 소모성 구매와 갱신 가능한 구독을 모두 지원한다. 앱이 소모성 구매만 지원할 예정이라면 크로스 플랫폼 라이브러리에서 구독 지원을 제외할 수 있다.
Clarence의 발표가 끝나고 짧은 Q&A 세션이 진행됐다. 흥미로운 점 중 하나는 Netflix에서 이러한 라이브러리를 위해 작성된 React Native 코드의 약 80%가 Android와 iOS 모두에서 공유된다는 사실이었다.
이번 밤의 마지막 발표는 Airbnb의 Leland Richardson가 맡았다. 그의 발표는 기존 코드베이스에 React Native를 도입한 Airbnb의 경험에 초점을 맞췄다. React Native로 새 앱을 만드는 것이 얼마나 쉬운지 이미 알고 있었기에, 기존 네이티브 앱에 React Native를 도입한 Airbnb의 경험에 큰 관심이 생겼다.
Leland은 먼저 그린필드(greenfield) 앱과 브라운필드(brownfield) 앱의 차이점을 설명했다. 그린필드는 기존 작업을 고려하지 않고 새 프로젝트를 시작하는 것을 의미한다. 반면 브라운필드는 기존 프로젝트의 요구사항, 개발 프로세스, 그리고 각 팀의 다양한 필요를 고려해야 한다.
그린필드 앱을 개발할 때는 React Native CLI가 Android와 iOS를 위한 단일 저장소를 설정하고 모든 것이 잘 작동한다. Airbnb에서 React Native를 도입할 때 첫 번째로 마주한 문제는 Android와 iOS 앱이 각각 별도의 저장소를 가지고 있다는 점이었다. 다중 저장소를 사용하는 회사는 React Native를 도입하기 전에 몇 가지 장벽을 넘어야 한다.
이 문제를 해결하기 위해 Airbnb는 먼저 React Native 코드베이스를 위한 새 저장소를 설정했다. 그들은 지속적 통합(CI) 서버를 사용해 Android와 iOS 저장소를 이 새 저장소에 미러링했다. 테스트가 실행되고 번들이 빌드된 후, 빌드 아티팩트는 다시 Android와 iOS 저장소로 동기화된다. 이렇게 하면 모바일 엔지니어들이 개발 환경을 변경하지 않고도 네이티브 코드를 작업할 수 있다. 모바일 엔지니어들은 npm을 설치하거나 패키저를 실행하거나 JavaScript 번들을 빌드할 필요가 없다. React Native 코드를 작성하는 엔지니어들도 Android와 iOS 간 코드를 동기화할 걱정 없이 React Native 저장소에서 직접 작업할 수 있다.
하지만 이 방법에는 단점도 있다. 가장 큰 문제는 원자적 업데이트를 배포할 수 없다는 점이다. 네이티브 코드와 JavaScript 코드가 모두 필요한 변경 사항은 세 개의 별도 풀 리퀘스트가 필요하며, 이 모든 것을 신중하게 적용해야 한다. 충돌을 피하기 위해 빌드가 시작된 이후 master 브랜치에 변경 사항이 생기면 CI는 Android와 iOS 저장소에 변경 사항을 적용하지 못한다. 이는 새로운 릴리즈가 나오는 날과 같이 커밋 빈도가 높은 날에는 긴 지연을 초래할 수 있다.
Airbnb는 이후 모노레포(mono repo) 방식으로 전환했다. 다행히 이 전환은 이미 고려 중이었고, Android와 iOS 팀이 React Native 사용에 익숙해지자 모노레포로의 전환을 가속화했다.
이 전환은 분리된 저장소 방식에서 발생했던 대부분의 문제를 해결했다. Leland은 이 방식이 버전 관리 서버에 더 큰 부하를 준다는 점도 언급했는데, 이는 규모가 작은 회사에서는 문제가 될 수 있다.
Leland의 발표 후반부는 내게도 관심 있는 주제인 React Native의 네비게이션 문제에 초점을 맞췄다. 그는 React Native에서 사용할 수 있는 다양한 네비게이션 라이브러리, 퍼스트파티와 서드파티 모두를 언급했다. NavigationExperimental은 처음에는 유망해 보였지만, 결국 그들의 사용 사례에는 적합하지 않았다.
실제로 기존의 어떤 네비게이션 라이브러리도 브라운필드 앱에 잘 맞지 않았다. 브라운필드 앱은 네비게이션 상태가 네이티브 앱에 완전히 소유되어야 한다. 예를 들어, React Native 뷰가 표시되는 동안 사용자 세션이 만료되면 네이티브 앱이 이를 인지하고 필요한 경우 로그인 화면을 표시할 수 있어야 한다.
Airbnb는 또한 전환 과정에서 네이티브 네비게이션 바를 자바스크립트 버전으로 대체하는 것을 피하고 싶었다. 그렇게 하면 사용자 경험이 어색해질 수 있기 때문이다. 초기에는 모달로 표시되는 뷰로 제한했지만, 이는 앱 전체에 React Native를 더 광범위하게 도입하는 데 문제가 되었다.
그들은 자체 라이브러리가 필요하다고 판단했다. 이 라이브러리의 이름은 airbnb-navigation이다. 이 라이브러리는 Airbnb의 코드베이스와 강하게 연결되어 있어 아직 오픈소스로 공개되지 않았지만, 연말까지 공개할 계획이다.
라이브러리의 API에 대해 자세히 설명하지는 않겠지만, 주요 내용은 다음과 같다:
각 씬(scene)을 미리 등록해야 한다.
각 씬은 자체 RCTRootView 내에서 표시된다. 각 플랫폼에서 네이티브로 표시된다(예: iOS에서는 UINavigationController를 사용).
씬의 주요 ScrollView는 ScrollScene 컴포넌트로 감싸야 한다. 이렇게 하면 iOS에서 상태 바를 탭해 상단으로 스크롤하는 것과 같은 네이티브 동작을 활용할 수 있다.
씬 간 전환은 네이티브로 처리되므로 성능에 대해 걱정할 필요가 없다.
Android 뒤로 가기 버튼이 자동으로 지원된다.
Navigator.Config UI-less 컴포넌트를 통해 뷰 컨트롤러 기반 네비게이션 바 스타일링을 활용할 수 있다.
또한 주의해야 할 몇 가지 사항이 있다:
네비게이션 바는 네이티브 컴포넌트이므로 자바스크립트에서 쉽게 커스터마이즈할 수 없다. 이는 의도된 설계로, 네이티브 네비게이션 바를 사용하는 것이 이 라이브러리의 핵심 요구사항이다.
ScreenProps는 브리지를 통해 전송될 때 직렬화/역직렬화되므로, 너무 많은 데이터를 전송할 때 주의해야 한다.
네비게이션 상태는 네이티브 앱이 소유하므로(이 역시 라이브러리의 핵심 요구사항), Redux와 같은 도구로 네비게이션 상태를 조작할 수 없다.
Leland의 발표 후에는 Q&A 세션이 이어졌다. 전반적으로 Airbnb는 React Native에 만족하고 있다. 그들은 App Store를 거치지 않고 Code Push를 사용해 문제를 해결하는 데 관심이 있으며, 엔지니어들은 Live Reload를 좋아한다. 사소한 변경 후에도 네이티브 앱을 다시 빌드할 필요가 없기 때문이다.
뛰어난 개발자 경험을 제공하려면 탄탄한 문서가 필수다. 좋은 문서를 만드는 데는 여러 요소가 필요하다. 이상적인 문서는 간결하고 유용하며 정확하고 완전해야 한다. 또한 사용자에게 즐거운 경험을 제공해야 한다. 최근 여러분의 피드백을 바탕으로 문서를 개선하기 위해 노력했고, 그 과정에서 이뤄진 몇 가지 개선 사항을 공유하고자 한다.
React Native의 일부 영역에서는 다양한 방법으로 작업을 수행할 수 있다. 여러분의 피드백을 통해 더 나은 가이드가 필요하다는 것을 알게 되었다.
새로운 네비게이션 가이드를 제공한다. 이 가이드에서는 Navigator, NavigatorIOS, NavigationExperimental 등 다양한 접근 방식을 비교하고 어떤 것을 사용해야 하는지 조언한다. 중기적으로는 이러한 인터페이스를 개선하고 통합하는 작업을 진행 중이다. 단기적으로는 더 나은 가이드가 여러분의 작업을 더 쉽게 만들어 줄 것이다.
또한 새로운 터치 처리 가이드도 준비했다. 이 가이드는 버튼과 같은 인터페이스를 만드는 기본 개념과 터치 이벤트를 처리하는 다양한 방법에 대해 간략히 설명한다.
React Native 개발 환경을 컴퓨터에 설정할 때, 여러 가지 설치와 설정 작업이 필요하다. 설치 과정을 정말 재미있고 흥미롭게 만들기는 어렵지만, 최소한 빠르고 간편하게 만들 수는 있다.
우리는 새로운 시작하기 워크플로우를 구축했다. 이 워크플로우는 개발 운영체제와 모바일 운영체제를 미리 선택할 수 있게 해서, 모든 설정 지침을 한 곳에 간결하게 제공한다. 또한 설치 과정을 직접 테스트하여 모든 것이 작동하는지 확인하고, 각 결정 지점에 명확한 권장 사항을 제공하도록 했다. 동료들을 대상으로 테스트한 결과, 이 방법이 개선된 것임을 확신한다.
또한 기존 앱에 React Native 통합하기 가이드도 작업했다. Facebook 앱과 같은 가장 큰 규모의 앱들 중 많은 수가 React Native로 앱의 일부를 구축하고, 나머지 부분은 일반 개발 도구로 만든다. 이 가이드가 더 많은 사람들이 이런 방식으로 앱을 개발하는 데 도움이 되길 바란다.
여러분의 피드백은 우리가 무엇을 우선순위로 해야 할지 알려준다. 어떤 사람들은 이 글을 읽고 "더 나은 문서? 휴. X의 문서는 여전히 엉망이야!"라고 생각할지도 모른다. 그건 좋다. 우리는 그런 에너지가 필요하다. 피드백을 주는 가장 좋은 방법은 피드백의 종류에 따라 다르다.
문서에서 잘못된 설명이나 실제로 작동하지 않는 코드와 같은 오류를 발견하면 이슈를 제출해라. "Documentation" 태그를 붙여서 적절한 사람에게 쉽게 전달될 수 있도록 해라.
특정한 오류는 없지만 문서의 어떤 부분이 근본적으로 혼란스럽다면, GitHub 이슈로 제출하기보다는 Canny에 해당 문서 영역에 대한 도움이 필요한 부분을 게시해라. 이렇게 하면 가이드 작성과 같은 일반적인 작업을 할 때 우선순위를 정하는 데 도움이 된다.
React Native를 오픈소스로 공개한 지 1년이 되었다. 소수의 엔지니어들이 시작한 아이디어는 이제 Facebook 내외의 제품 팀들이 사용하는 프레임워크로 성장했다. 오늘 F8에서 Microsoft가 React Native를 Windows 생태계로 가져온다고 발표했다. 이를 통해 개발자들은 Windows PC, Phone, Xbox에서 React Native를 구축할 수 있게 된다. 또한 Visual Studio Code용 React Native 확장과 CodePush 같은 오픈소스 도구와 서비스를 제공해 Windows 플랫폼에서 React Native 앱을 개발하는 데 도움을 줄 예정이다. 한편, Samsung은 하이브리드 플랫폼을 위해 React Native를 개발 중이다. 이를 통해 개발자들은 수백만 대의 SmartTV, 모바일, 웨어러블 기기를 위한 앱을 만들 수 있게 된다. 또한 React Native용 Facebook SDK를 공개해 개발자들이 로그인, 공유, 앱 분석, Graph API 같은 Facebook 소셜 기능을 앱에 쉽게 통합할 수 있게 했다. 1년 만에 React Native는 모든 주요 플랫폼에서 개발자들이 앱을 만드는 방식을 바꿔놓았다.
이것은 대단한 여정이었지만, 우리는 이제 막 시작한 것이다. 지난 1년간 React Native가 어떻게 성장하고 진화했는지, 그 과정에서 마주한 도전들, 그리고 앞으로 기대되는 점들을 돌아보자.
React Native를 사용하면 React와 Relay의 선언적 프로그래밍 모델을 통해 JavaScript로 Android와 iOS 앱을 개발할 수 있다. 이를 통해 더 간결하고 이해하기 쉬운 코드를 작성할 수 있으며, 컴파일 과정 없이 빠르게 반복 작업을 진행할 수 있다. 또한 여러 플랫폼 간에 코드를 쉽게 공유할 수 있다. 이렇게 하면 더 빠르게 앱을 출시할 수 있고, 앱의 외관과 사용자 경험을 훌륭하게 만드는 데 집중할 수 있다. 성능 최적화는 이 과정에서 매우 중요한 부분이다. 다음은 React Native 앱의 시작 속도를 두 배로 향상시킨 과정에 대한 이야기이다.
더 빠르게 동작하는 앱은 콘텐츠를 신속하게 로드한다. 사용자는 더 많은 시간을 앱과 상호작용할 수 있고, 부드러운 애니메이션은 앱 사용을 즐겁게 만든다. 2011년형 폰과 2G 네트워크가 대부분인 신흥 시장에서는 성능에 초점을 맞추는 것이 앱 사용 가능 여부를 결정짓는 중요한 요소가 된다.
React Native를 iOS와 Android에 출시한 이후, 우리는 리스트 뷰 스크롤 성능, 메모리 효율성, UI 반응성, 앱 시작 시간을 지속적으로 개선해왔다. 시작 시간은 앱의 첫인상을 결정짓고 프레임워크의 모든 부분에 부담을 주기 때문에, 가장 보람있고 도전적인 문제다.
React Native의 목표는 최고의 개발자 경험을 제공하는 것이다. 그중 중요한 부분은 파일을 저장하고 변경 사항을 확인할 때까지 걸리는 시간이다. 앱이 커질수록 이 피드백 루프를 1초 미만으로 단축하는 것이 목표이다.
이 목표에 가까워지기 위해 세 가지 주요 기능을 도입했다:
자바스크립트를 사용해 컴파일 시간을 줄인다.
es6/flow/jsx 파일을 VM이 이해할 수 있는 일반 자바스크립트로 변환하는 Packager 도구를 구현했다. 이 도구는 서버로 설계되어 중간 상태를 메모리에 유지해 빠른 증분 변경을 가능하게 하고, 멀티코어를 활용한다.
파일 저장 시 앱을 다시 로드하는 Live Reload 기능을 구축했다.
현재 개발자들의 병목 현상은 앱을 다시 로드하는 시간이 아니라 앱 상태를 잃는 문제이다. 일반적인 시나리오는 런치 스크린에서 여러 화면을 거쳐야 하는 기능을 작업하는 경우이다. 매번 다시 로드할 때마다 동일한 경로를 반복해 클릭해야 하기 때문에 피드백 루프가 몇 초씩 길어지게 된다.
핫 리로딩은 Hot Module Replacement(HMR)라는 기능 위에 구축되었다. 이 기능은 webpack에서 처음 도입했고, React Native Packager 내부에 구현했다. HMR은 Packager가 파일 변경을 감지하고, 앱에 포함된 간단한 HMR 런타임으로 HMR 업데이트를 전송한다.
간단히 말해, HMR 업데이트는 변경된 JS 모듈의 새 코드를 포함한다. 런타임이 이를 받으면, 이전 모듈의 코드를 새 코드로 대체한다:
HMR 업데이트는 단순히 변경할 모듈의 코드만 포함하지 않는다. 코드를 교체하는 것만으로는 런타임이 변경 사항을 반영하기에 충분하지 않기 때문이다. 문제는 모듈 시스템이 이미 업데이트하려는 모듈의 _exports_를 캐시했을 가능성이다. 예를 들어, 다음과 같은 두 모듈로 구성된 앱이 있다고 가정하자:
앱이 번들링될 때, React Native는 __d 함수를 사용해 각 모듈을 모듈 시스템에 등록한다. 이 앱의 경우, 여러 __d 정의 중 log에 대한 정의가 하나 있을 것이다:
__d('log',function(){ ...// 모듈의 코드 });
이 호출은 각 모듈의 코드를 익명 함수로 감싸며, 이를 일반적으로 팩토리 함수라고 부른다. 모듈 시스템 런타임은 각 모듈의 팩토리 함수, 이미 실행되었는지 여부, 그리고 실행 결과(exports)를 추적한다. 모듈이 필요할 때, 모듈 시스템은 이미 캐시된 exports를 제공하거나 모듈의 팩토리 함수를 처음 실행하고 결과를 저장한다.
앱을 시작하고 log를 요청한다고 가정하자. 이 시점에서 log와 time의 팩토리 함수는 아직 실행되지 않았으므로 exports가 캐시되지 않았다. 그런 다음 사용자가 time을 수정해 날짜를 MM/DD 형식으로 반환하도록 변경한다:
// time.js functionbar(){ const date =newDate(); return`${date.getMonth()+1}/${date.getDate()}`; } module.exports= bar;
Packager는 time의 새 코드를 런타임으로 전송한다(1단계). 그리고 log가 결국 요청되면, time의 변경 사항이 반영된 상태로 exported 함수가 실행된다(2단계):
이제 log 코드가 time을 최상위 require로 요청한다고 가정하자:
const time =require('./time');// 최상위 require // log.js functionlog(message){ console.log(`[${time()}] ${message}`); } module.exports= log;
log가 요청되면, 런타임은 log와 time의 exports를 캐시한다(1단계). 그런 다음 time이 수정되면, HMR 프로세스는 단순히 time의 코드를 교체하는 것으로 끝날 수 없다. 그렇게 하면 log가 실행될 때 time의 캐시된 복사본(이전 코드)을 사용하게 된다.
log가 time의 변경 사항을 반영하려면, log의 캐시된 exports를 지워야 한다. 왜냐하면 log가 의존하는 모듈 중 하나가 핫 스왑되었기 때문이다(3단계). 마지막으로 log가 다시 요청되면, 팩토리 함수가 실행되면서 time을 요청하고 새 코드를 가져온다.
React Native의 HMR(Hot Module Replacement)은 hot 객체를 도입해 모듈 시스템을 확장한다. 이 API는 webpack의 HMR API를 기반으로 한다. hot 객체는 accept라는 함수를 제공하는데, 이 함수를 사용해 모듈이 핫 스왑될 때 실행될 콜백을 정의할 수 있다. 예를 들어, time의 코드를 다음과 같이 변경하면, time을 저장할 때마다 콘솔에 "time changed"가 출력된다:
// time.js functiontime(){ ...// 새로운 코드 } module.hot.accept(()=>{ console.log('time changed'); }); module.exports= time;
이 API를 직접 사용해야 하는 경우는 드물다. 대부분의 일반적인 사용 사례에서는 핫 리로딩이 기본적으로 동작한다.
앞서 살펴봤듯이, HMR 업데이트를 단순히 수락하는 것만으로는 충분하지 않다. 이미 실행된 모듈이 핫 스왑된 모듈을 사용하고 있을 수 있고, 해당 모듈의 임포트가 캐시된 상태일 수 있다. 예를 들어, 영화 앱 예제의 의존성 트리에서 최상위에 MovieRouter가 있고, 이 모듈이 MovieSearch와 MovieScreen 뷰에 의존하며, 이 뷰들은 이전 예제의 log와 time 모듈에 의존한다고 가정해 보자.
사용자가 영화 검색 뷰에 접근했지만 다른 뷰에는 접근하지 않았다면, MovieScreen을 제외한 모든 모듈의 익스포트가 캐시된다. time 모듈에 변경이 발생하면, 런타임은 log의 익스포트를 지워 time의 변경 사항을 반영해야 한다. 이 프로세스는 여기서 끝나지 않는다. 런타임은 모든 부모 모듈이 업데이트를 수락할 때까지 이 과정을 재귀적으로 반복한다. 따라서 log에 의존하는 모듈들을 가져와 업데이트를 시도한다. MovieScreen의 경우 아직 필요하지 않았기 때문에 중단할 수 있다. MovieSearch의 경우 익스포트를 지우고 부모 모듈을 재귀적으로 처리해야 한다. 마지막으로 MovieRouter에 대해 동일한 작업을 수행하고, 더 이상 의존하는 모듈이 없으므로 프로세스를 종료한다.
런타임은 의존성 트리를 탐색하기 위해 HMR 업데이트 시 Packager로부터 역의존성 트리를 받는다. 이 예제에서 런타임은 다음과 같은 JSON 객체를 받게 된다:
리액트 컴포넌트는 핫 리로딩과 함께 사용하기가 조금 더 까다롭다. 문제는 기존 코드를 새로운 코드로 단순히 교체할 수 없다는 점이다. 그렇게 하면 컴포넌트의 상태를 잃어버리기 때문이다. 리액트 웹 애플리케이션의 경우, Dan Abramov가 이 문제를 해결하기 위해 웹팩의 HMR API를 사용하는 바벨 트랜스폼을 구현했다. 간단히 말해, 그의 솔루션은 _트랜스폼 시점_에 모든 리액트 컴포넌트에 대한 프록시를 생성하는 방식으로 동작한다. 프록시는 컴포넌트의 상태를 유지하고, 실제 컴포넌트에 라이프사이클 메서드를 위임한다. 이 실제 컴포넌트가 핫 리로딩의 대상이 된다:
프록시 컴포넌트를 생성하는 것 외에도, 이 트랜스폼은 리액트가 컴포넌트를 리렌더링하도록 강제하는 코드를 포함한 accept 함수를 정의한다. 이 방식을 통해 앱의 상태를 잃지 않고 렌더링 코드를 핫 리로딩할 수 있다.
리액트 네이티브에 기본으로 포함된 트랜스포머는 babel-preset-react-native를 사용한다. 이 프리셋은 웹팩을 사용하는 리액트 웹 프로젝트와 동일한 방식으로 react-transform을 사용하도록 설정되어 있다.
리듀서를 변경하면, 해당 리듀서를 수락하는 코드가 클라이언트로 전송된다. 그런 다음 클라이언트는 리듀서가 스스로를 수락하는 방법을 모른다는 것을 인식하고, 해당 리듀서를 참조하는 모든 모듈을 찾아 수락하려고 시도한다. 결국, 이 흐름은 단일 스토어인 configureStore 모듈에 도달하여 HMR 업데이트를 수락하게 된다.
웹에서 React, 모바일에서 React Native가 출시되면서 개발자들이 제품을 구축할 수 있는 새로운 프론트엔드 프레임워크를 제공했다. 견고한 제품을 만들기 위한 핵심 요소 중 하나는 시각 장애가 있거나 다른 장애를 가진 사람들을 포함해 누구나 사용할 수 있도록 보장하는 것이다. React와 React Native의 접근성 API를 사용하면 시각 장애인을 위한 스크린 리더 같은 보조 기술을 사용하는 사람들도 React 기반 경험을 활용할 수 있다.
이 글에서는 React Native 앱에 초점을 맞춘다. React 접근성 API는 Android와 iOS API와 유사하게 디자인했다. Android, iOS 또는 웹을 위해 접근성 있는 애플리케이션을 개발해본 경험이 있다면 React AX API의 프레임워크와 용어에 익숙할 것이다. 예를 들어, UI 엘리먼트를 _accessible_로 설정해(따라서 보조 기술에 노출) 엘리먼트에 대한 문자열 설명을 제공하기 위해 _accessibilityLabel_을 사용할 수 있다:
<Viewaccessible={true}accessibilityLabel="This is simple view">
Facebook의 React 기반 제품 중 하나인 Ads Manager 앱을 살펴보며 React AX API를 조금 더 깊이 있게 적용하는 방법을 알아보자.
올해 초, 우리는 iOS용 React Native를 소개했다. React Native는 웹에서 React를 사용하던 개발자들에게 익숙한 선언적이고 독립적인 UI 컴포넌트와 빠른 개발 사이클을 모바일 플랫폼에 가져오면서도, 네이티브 애플리케이션의 속도, 정확성, 그리고 느낌을 유지한다. 오늘, 우리는 Android용 React Native를 출시하게 되어 기쁘게 생각한다.
Facebook에서는 이미 1년 넘게 React Native를 프로덕션 환경에서 사용해 왔다. 거의 정확히 1년 전, 우리 팀은 Ads Manager 앱을 개발하기 시작했다. 우리의 목표는 Facebook에서 광고를 하는 수백만 명의 사람들이 계정을 관리하고 이동 중에도 새로운 광고를 생성할 수 있는 새로운 앱을 만드는 것이었다. 이 앱은 Facebook의 첫 번째 완전한 React Native 앱이자 첫 번째 크로스 플랫폼 앱이 되었다. 이 글에서 우리는 이 앱을 어떻게 구축했는지, React Native가 어떻게 더 빠르게 개발할 수 있게 했는지, 그리고 우리가 배운 교훈을 공유하고자 한다.