Skip to main content

0.36: Headless JS, the Keyboard API, & more

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

오늘 React Native 0.36을 출시한다. 새로운 기능에 대해 자세히 알아보자.

Headless JS

Headless JS는 앱이 백그라운드에 있을 때 JavaScript로 작업을 실행하는 방법이다. 최신 데이터를 동기화하거나, 푸시 알림을 처리하거나, 음악을 재생하는 등의 용도로 사용할 수 있다. 현재는 Android에서만 사용 가능하다.

시작하려면 전용 파일(예: SomeTaskName.js)에 비동기 작업을 정의한다:

module.exports = async taskData => {
// 여기서 작업을 수행한다.
};

그 다음, AppRegistry에 작업을 등록한다:

AppRegistry.registerHeadlessTask('SomeTaskName', () =>
require('SomeTaskName'),
);

Headless JS를 사용하려면 필요할 때 서비스를 시작할 수 있도록 네이티브 Java 코드를 작성해야 한다. 더 자세한 내용은 Headless JS 문서를 참고한다!

키보드 API

Keyboard를 사용하면 화면 키보드를 더 쉽게 다룰 수 있다. 네이티브 키보드 이벤트를 감지하고 반응할 수 있다. 예를 들어, 활성화된 키보드를 닫으려면 Keyboard.dismiss()를 호출하면 된다:

import {Keyboard} from 'react-native';

// 키보드를 닫는다!
Keyboard.dismiss();

애니메이션 나눗셈

React Native에서는 이미 두 애니메이션 값을 더하거나, 곱하거나, 모듈로 연산하는 기능을 지원한다. 버전 0.36부터는 두 애니메이션 값을 나누는 기능도 추가되었다. 특정 경우에는 계산을 위해 한 애니메이션 값을 다른 애니메이션 값으로 역수로 만들어야 할 때가 있다. 예를 들어 스케일을 역수로 만드는 경우가 있다 (2x --> 0.5x):

const a = Animated.Value(1);
const b = Animated.divide(1, a);

Animated.spring(a, {
toValue: 2,
}).start();

ba의 스프링 애니메이션을 따라가며 1 / a 값을 생성한다.

기본 사용법은 다음과 같다:

<Animated.View style={{transform: [{scale: a}]}}>
<Animated.Image style={{transform: [{scale: b}]}} />
<Animated.View>

이 예제에서 내부 이미지는 부모의 스케일링이 상쇄되기 때문에 전혀 늘어나지 않는다. 더 자세히 알고 싶다면 애니메이션 가이드를 참고한다.

다크 상태 바

StatusBar에 새로운 barStyle 값인 dark-content가 추가되었다. 이제 barStyle을 Android와 iOS 모두에서 사용할 수 있다. 각 값의 동작 방식은 다음과 같다:

  • default: 플랫폼 기본값을 사용한다(iOS에서는 밝은 색, Android에서는 어두운 색).
  • light-content: 검은색 텍스트와 아이콘이 있는 밝은 상태 바를 사용한다.
  • dark-content: 흰색 텍스트와 아이콘이 있는 어두운 상태 바를 사용한다.

그리고 더 많은 변화들

위 내용은 0.36 버전에서 변경된 사항의 일부에 불과하다. 새로운 기능, 버그 수정, 주요 변경 사항의 전체 목록을 확인하려면 GitHub의 릴리스 노트를 참고한다.

터미널에서 다음 명령어를 실행해 0.36 버전으로 업그레이드할 수 있다:

$ npm install --save react-native@0.36
$ react-native upgrade

Right-to-Left Layout Support For React Native Apps

· 13 min read
Mengjue (Mandy) Wang
Software Engineer Intern at Facebook

앱 스토어에 앱을 출시한 후, 다음 단계는 더 많은 사용자를 확보하기 위해 국제화를 진행하는 것이다. 전 세계 20개 이상의 국가와 수많은 사람들이 오른쪽에서 왼쪽으로 쓰는 언어(RTL)를 사용한다. 따라서 앱이 RTL을 지원하도록 만드는 것이 필요하다.

React Native가 RTL 레이아웃을 지원하도록 개선되었다는 소식을 전하게 되어 기쁘다. 이 기능은 현재 react-native 마스터 브랜치에서 사용할 수 있으며, 다음 RC 버전인 v0.33.0-rc에서도 제공될 예정이다.

이를 위해 RN이 사용하는 핵심 레이아웃 엔진인 css-layout과 RN 코어 구현, 그리고 특정 오픈소스 JS 컴포넌트를 RTL 지원을 위해 변경했다.

RTL 지원을 실제 환경에서 테스트하기 위해, 최신 버전의 Facebook Ads Manager 앱(첫 번째 크로스 플랫폼 100% RN 앱)이 아랍어와 히브리어로 iOSAndroid에서 RTL 레이아웃으로 제공된다. 다음은 RTL 언어로 된 앱의 모습이다:

RN의 RTL 지원을 위한 주요 변경 사항

css-layout은 이미 레이아웃을 위해 startend 개념을 도입했다. Left-to-Right(LTR) 레이아웃에서 startleft를 의미하고, endright를 의미한다. 하지만 RTL 레이아웃에서는 startright를, endleft를 의미한다. 이를 통해 RN은 startend 계산에 의존해 position, padding, margin을 포함한 올바른 레이아웃을 계산할 수 있다.

또한 css-layout은 각 컴포넌트의 방향을 부모로부터 상속받도록 설계했다. 따라서 루트 컴포넌트의 방향을 RTL로 설정하면 전체 앱이 뒤집힌다.

아래 다이어그램은 이러한 변경 사항을 높은 수준에서 설명한다:

주요 변경 사항은 다음과 같다:

이 업데이트를 통해 앱에서 RTL 레이아웃을 허용하면:

  • 모든 컴포넌트 레이아웃이 수평으로 뒤집힌다.
  • RTL을 지원하는 OSS 컴포넌트를 사용 중이라면 일부 제스처와 애니메이션이 자동으로 RTL 레이아웃을 적용한다.
  • 앱을 완전히 RTL 지원으로 만들기 위해 필요한 추가 작업은 최소화된다.

앱을 RTL 지원으로 만들기

  1. RTL을 지원하려면 먼저 앱에 RTL 언어 번들을 추가한다.

    • iOSAndroid의 일반 가이드를 참고한다.
  2. 앱이 RTL 레이아웃을 지원할 수 있도록 네이티브 코드 시작 부분에서 allowRTL() 함수를 호출한다. 이 유틸리티는 앱이 준비되었을 때만 RTL 레이아웃을 적용한다. 예제는 다음과 같다.

    iOS:

    // AppDelegate.m 파일에서
    [[RCTI18nUtil sharedInstance] allowRTL:YES];

    Android:

    // MainActivity.java 파일에서
    I18nUtil sharedI18nUtilInstance = I18nUtil.getInstance();
    sharedI18nUtilInstance.allowRTL(context, true);
  3. Android의 경우 AndroidManifest.xml 파일의 <application> 엘리먼트에 android:supportsRtl="true"를 추가한다.

이제 앱을 다시 컴파일하고 기기 언어를 RTL 언어(예: 아랍어 또는 히브리어)로 변경하면 앱 레이아웃이 자동으로 RTL로 변경된다.

RTL 대응 컴포넌트 작성하기

일반적으로 대부분의 컴포넌트는 이미 RTL(오른쪽에서 왼쪽) 대응이 되어 있다. 예를 들어:

  • 왼쪽에서 오른쪽으로 배치된 레이아웃
  • 오른쪽에서 왼쪽으로 배치된 레이아웃

하지만 몇 가지 주의해야 할 경우가 있으며, 이때는 I18nManager를 사용해야 한다. I18nManager에는 isRTL이라는 상수가 있어 앱의 레이아웃이 RTL인지 아닌지를 확인할 수 있다. 이를 통해 레이아웃에 따라 필요한 변경을 적용할 수 있다.

방향성을 가진 아이콘 처리

컴포넌트에 아이콘이나 이미지를 사용할 때, RN은 소스 이미지를 자동으로 뒤집지 않기 때문에 LTR과 RTL 레이아웃에서 동일하게 표시된다. 따라서 레이아웃 스타일에 따라 아이콘을 직접 뒤집어야 한다.

  • 좌에서 우로(LTR) 레이아웃
  • 우에서 좌로(RTL) 레이아웃

아이콘의 방향을 조정하는 두 가지 방법은 다음과 같다:

  • 이미지 컴포넌트에 transform 스타일 추가:

    <Image
    source={...}
    style={{transform: [{scaleX: I18nManager.isRTL ? -1 : 1}]}}
    />
  • 또는, 방향에 따라 이미지 소스 변경:

    let imageSource = require('./back.png');
    if (I18nManager.isRTL) {
    imageSource = require('./forward.png');
    }
    return <Image source={imageSource} />;

제스처와 애니메이션

안드로이드와 iOS 개발에서 RTL 레이아웃으로 변경하면, 제스처와 애니메이션이 LTR 레이아웃과 반대 방향으로 동작한다. 현재 리액트 네이티브(RN)에서는 제스처와 애니메이션이 RN 코어 코드 수준에서 지원되지 않고, 컴포넌트 수준에서 지원된다. 다행히 일부 컴포넌트는 이미 RTL을 지원하고 있으며, 예를 들어 SwipeableRowNavigationExperimental가 있다. 그러나 제스처를 사용하는 다른 컴포넌트들은 수동으로 RTL을 지원해야 한다.

제스처 RTL 지원을 잘 보여주는 예로 SwipeableRow를 들 수 있다.

제스처 예제
// SwipeableRow.js
_isSwipingExcessivelyRightFromClosedPosition(gestureState: Object): boolean {
// ...
const gestureStateDx = IS_RTL ? -gestureState.dx : gestureState.dx;
return (
this._isSwipingRightFromClosed(gestureState) &&
gestureStateDx > RIGHT_SWIPE_THRESHOLD
);
},
애니메이션 예제
// SwipeableRow.js
_animateBounceBack(duration: number): void {
// ...
const swipeBounceBackDistance = IS_RTL ?
-RIGHT_SWIPE_BOUNCE_BACK_DISTANCE :
RIGHT_SWIPE_BOUNCE_BACK_DISTANCE;
this._animateTo(
-swipeBounceBackDistance,
duration,
this._animateToClosedPositionDuringBounce,
);
},

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 지원을 더 개발자 친화적으로 만드는 방법을 찾는 것이 이상적일 것이다.

직접 해보기!

RTL 지원에 대해 더 깊이 이해하려면 RNTesterRTLExample을 확인해 보세요. 여러분의 경험을 공유해 주시면 감사하겠습니다!

마지막으로, 이 글을 읽어 주셔서 감사합니다! React Native의 RTL 지원이 여러분의 앱을 전 세계 사용자에게 더 나은 경험을 제공하는 데 도움이 되길 바랍니다!

San Francisco Meetup Recap

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

지난주에 Zynga의 샌프란시스코 사무실에서 열린 React Native Meetup에 참석할 기회가 있었다. 약 200명이 참석한 이 행사는 React Native에 관심이 있는 주변 개발자들을 만나기 좋은 자리였다.

나는 특히 Zynga, Netflix, Airbnb와 같은 회사에서 React와 React Native를 어떻게 사용하는지 더 깊이 알아보는 데 관심이 있었다. 그날 밤의 주제는 다음과 같았다:

  • React를 활용한 빠른 프로토타이핑
  • React Native를 위한 API 설계
  • 기존 코드베이스에서 React Native 사용하기: 차이를 좁히는 방법

하지만 먼저, 행사는 간단한 소개와 최근 소식으로 시작되었다:

만약 이런 모임이 여러분 근처에서 열린다면, 꼭 참석해 보길 추천한다!

Zynga에서 React를 활용한 빠른 프로토타이핑

첫 번째 소식에 이어 저녁 행사의 주최사인 Zynga가 간단한 소개를 진행했다. 아비쉐크 차다는 모바일에서 새로운 경험을 빠르게 프로토타이핑하기 위해 React를 어떻게 사용하는지 설명했다. Draw Something와 유사한 앱의 빠른 프로토타입을 시연했다. 그들은 React Native와 유사한 접근 방식을 사용해 브리지를 통해 네이티브 API에 접근한다. 아비쉐크가 기기의 카메라를 사용해 관객의 사진을 찍은 다음, 누군가의 머리에 모자를 그리는 것으로 이를 시연했다.

Netflix에서 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 모두에서 공유된다는 사실이었다.

기존 코드베이스에 React Native 통합하기

이번 밤의 마지막 발표는 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를 사용).
  • 씬의 주요 ScrollViewScrollScene 컴포넌트로 감싸야 한다. 이렇게 하면 iOS에서 상태 바를 탭해 상단으로 스크롤하는 것과 같은 네이티브 동작을 활용할 수 있다.
  • 씬 간 전환은 네이티브로 처리되므로 성능에 대해 걱정할 필요가 없다.
  • Android 뒤로 가기 버튼이 자동으로 지원된다.
  • Navigator.Config UI-less 컴포넌트를 통해 뷰 컨트롤러 기반 네비게이션 바 스타일링을 활용할 수 있다.

또한 주의해야 할 몇 가지 사항이 있다:

  • 네비게이션 바는 네이티브 컴포넌트이므로 자바스크립트에서 쉽게 커스터마이즈할 수 없다. 이는 의도된 설계로, 네이티브 네비게이션 바를 사용하는 것이 이 라이브러리의 핵심 요구사항이다.
  • ScreenProps는 브리지를 통해 전송될 때 직렬화/역직렬화되므로, 너무 많은 데이터를 전송할 때 주의해야 한다.
  • 네비게이션 상태는 네이티브 앱이 소유하므로(이 역시 라이브러리의 핵심 요구사항), Redux와 같은 도구로 네비게이션 상태를 조작할 수 없다.

Leland의 발표 후에는 Q&A 세션이 이어졌다. 전반적으로 Airbnb는 React Native에 만족하고 있다. 그들은 App Store를 거치지 않고 Code Push를 사용해 문제를 해결하는 데 관심이 있으며, 엔지니어들은 Live Reload를 좋아한다. 사소한 변경 후에도 네이티브 앱을 다시 빌드할 필요가 없기 때문이다.

마무리

이번 행사는 몇 가지 React Native 관련 소식으로 마무리했다:

  • Deco는 React Native Showcase를 발표하고, 모두가 자신의 앱을 목록에 추가하도록 초대했다.
  • 최근에 진행된 문서 개편이 소개됐다!
  • Deco IDE의 창시자 중 한 명인 Devin Abbott가 React Native 강좌를 진행할 예정이다.

미트업은 커뮤니티의 다른 개발자들을 만나고 배울 수 있는 좋은 기회다. 앞으로 더 많은 React Native 미트업에 참석할 생각이다. 여러분도 미트업에 참여한다면, 저를 찾아 React Native를 더 잘 활용할 수 있는 방법에 대해 이야기해보자!

Toward Better Documentation

· 8 min read
Kevin Lacker
Engineering Manager at Facebook

뛰어난 개발자 경험을 제공하려면 탄탄한 문서가 필수다. 좋은 문서를 만드는 데는 여러 요소가 필요하다. 이상적인 문서는 간결하고 유용하며 정확하고 완전해야 한다. 또한 사용자에게 즐거운 경험을 제공해야 한다. 최근 여러분의 피드백을 바탕으로 문서를 개선하기 위해 노력했고, 그 과정에서 이뤄진 몇 가지 개선 사항을 공유하고자 한다.

인라인 예제

새로운 라이브러리, 프로그래밍 언어, 프레임워크를 배울 때 가장 아름다운 순간은 코드를 처음 작성하고 실행해 보며 실제로 작동하는 것을 확인하는 순간이다. 여러분이 실제로 무언가를 만들어냈을 때 느끼는 그 감동을 문서에 담고 싶었다. 다음과 같이 말이다:

import React, { Component } from 'react';
import { AppRegistry, Text, View } from 'react-native';

class ScratchPad extends Component {
render() {
return (
<View style={{flex: 1}}>
<Text style={{fontSize: 30, flex: 1, textAlign: 'center'}}>
Isn't this cool?
</Text>
<Text style={{fontSize: 100, flex: 1, textAlign: 'center'}}>
👍
</Text>
</View>
);
}
}

AppRegistry.registerComponent('ScratchPad', () => ScratchPad);

Devin Abbott의 도움으로 react-native-web-player 모듈을 사용한 이 인라인 예제는 React Native의 기본을 배우는 데 매우 효과적인 방법이다. 우리는 React Native 초보자를 위한 튜토리얼을 가능한 한 이러한 예제를 사용하도록 업데이트했다. 예제 코드를 조금만 수정해도 어떤 결과가 나오는지 궁금하다면, 이 방법이 직접 실험해 보기에 매우 적합하다. 또한 개발자 도구를 만들고 있다면, 여러분의 사이트에 React Native 샘플을 라이브로 보여주고 싶을 때 react-native-web-player를 사용하면 간단히 구현할 수 있다.

코어 시뮬레이션 엔진은 Nicolas Gallagherreact-native-web 프로젝트에서 제공된다. 이 프로젝트는 TextView와 같은 React Native 컴포넌트를 웹에서 표시할 수 있는 방법을 제공한다. 모바일과 웹 경험을 공유하는 코드베이스를 구축하고 싶다면 react-native-web을 확인해 보길 바란다.

더 나은 가이드

React Native의 일부 영역에서는 다양한 방법으로 작업을 수행할 수 있다. 여러분의 피드백을 통해 더 나은 가이드가 필요하다는 것을 알게 되었다.

새로운 네비게이션 가이드를 제공한다. 이 가이드에서는 Navigator, NavigatorIOS, NavigationExperimental 등 다양한 접근 방식을 비교하고 어떤 것을 사용해야 하는지 조언한다. 중기적으로는 이러한 인터페이스를 개선하고 통합하는 작업을 진행 중이다. 단기적으로는 더 나은 가이드가 여러분의 작업을 더 쉽게 만들어 줄 것이다.

또한 새로운 터치 처리 가이드도 준비했다. 이 가이드는 버튼과 같은 인터페이스를 만드는 기본 개념과 터치 이벤트를 처리하는 다양한 방법에 대해 간략히 설명한다.

플렉스 박스(Flexbox) 영역도 개선했다. 플렉스 박스를 사용한 레이아웃 처리컴포넌트 크기 조절에 대한 튜토리얼을 포함한다. 또한, React Native에서 레이아웃을 제어하는 모든 props 목록도 제공한다. 이 목록은 간단하지만 유용한 정보를 담고 있다.

시작하기

React Native 개발 환경을 컴퓨터에 설정할 때, 여러 가지 설치와 설정 작업이 필요하다. 설치 과정을 정말 재미있고 흥미롭게 만들기는 어렵지만, 최소한 빠르고 간편하게 만들 수는 있다.

우리는 새로운 시작하기 워크플로우를 구축했다. 이 워크플로우는 개발 운영체제와 모바일 운영체제를 미리 선택할 수 있게 해서, 모든 설정 지침을 한 곳에 간결하게 제공한다. 또한 설치 과정을 직접 테스트하여 모든 것이 작동하는지 확인하고, 각 결정 지점에 명확한 권장 사항을 제공하도록 했다. 동료들을 대상으로 테스트한 결과, 이 방법이 개선된 것임을 확신한다.

또한 기존 앱에 React Native 통합하기 가이드도 작업했다. Facebook 앱과 같은 가장 큰 규모의 앱들 중 많은 수가 React Native로 앱의 일부를 구축하고, 나머지 부분은 일반 개발 도구로 만든다. 이 가이드가 더 많은 사람들이 이런 방식으로 앱을 개발하는 데 도움이 되길 바란다.

여러분의 도움이 필요합니다

여러분의 피드백은 우리가 무엇을 우선순위로 해야 할지 알려준다. 어떤 사람들은 이 글을 읽고 "더 나은 문서? 휴. X의 문서는 여전히 엉망이야!"라고 생각할지도 모른다. 그건 좋다. 우리는 그런 에너지가 필요하다. 피드백을 주는 가장 좋은 방법은 피드백의 종류에 따라 다르다.

문서에서 잘못된 설명이나 실제로 작동하지 않는 코드와 같은 오류를 발견하면 이슈를 제출해라. "Documentation" 태그를 붙여서 적절한 사람에게 쉽게 전달될 수 있도록 해라.

특정한 오류는 없지만 문서의 어떤 부분이 근본적으로 혼란스럽다면, GitHub 이슈로 제출하기보다는 Canny에 해당 문서 영역에 대한 도움이 필요한 부분을 게시해라. 이렇게 하면 가이드 작성과 같은 일반적인 작업을 할 때 우선순위를 정하는 데 도움이 된다.

여기까지 읽어줘서 고맙고, React Native를 사용해줘서 고맙다!

React Native: A year in review

· 3 min read
Martin Konicek
Facebook 소프트웨어 엔지니어

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가 어떻게 성장하고 진화했는지, 그 과정에서 마주한 도전들, 그리고 앞으로 기대되는 점들을 돌아보자.

이 글은 발췌문이다. 전체 글은 Facebook Code에서 확인할 수 있다.

Dive into React Native Performance

· 3 min read
Pieter De Baets
Software Engineer at Facebook

React Native를 사용하면 React와 Relay의 선언적 프로그래밍 모델을 통해 JavaScript로 Android와 iOS 앱을 개발할 수 있다. 이를 통해 더 간결하고 이해하기 쉬운 코드를 작성할 수 있으며, 컴파일 과정 없이 빠르게 반복 작업을 진행할 수 있다. 또한 여러 플랫폼 간에 코드를 쉽게 공유할 수 있다. 이렇게 하면 더 빠르게 앱을 출시할 수 있고, 앱의 외관과 사용자 경험을 훌륭하게 만드는 데 집중할 수 있다. 성능 최적화는 이 과정에서 매우 중요한 부분이다. 다음은 React Native 앱의 시작 속도를 두 배로 향상시킨 과정에 대한 이야기이다.

왜 속도가 중요한가?

더 빠르게 동작하는 앱은 콘텐츠를 신속하게 로드한다. 사용자는 더 많은 시간을 앱과 상호작용할 수 있고, 부드러운 애니메이션은 앱 사용을 즐겁게 만든다. 2011년형 폰2G 네트워크가 대부분인 신흥 시장에서는 성능에 초점을 맞추는 것이 앱 사용 가능 여부를 결정짓는 중요한 요소가 된다.

React Native를 iOSAndroid에 출시한 이후, 우리는 리스트 뷰 스크롤 성능, 메모리 효율성, UI 반응성, 앱 시작 시간을 지속적으로 개선해왔다. 시작 시간은 앱의 첫인상을 결정짓고 프레임워크의 모든 부분에 부담을 주기 때문에, 가장 보람있고 도전적인 문제다.

이 글은 발췌문이다. 전체 글은 Facebook Code에서 확인할 수 있다.

Introducing Hot Reloading

· 16 min read
Martín Bigio
Software Engineer at Instagram

React Native의 목표는 최고의 개발자 경험을 제공하는 것이다. 그중 중요한 부분은 파일을 저장하고 변경 사항을 확인할 때까지 걸리는 시간이다. 앱이 커질수록 이 피드백 루프를 1초 미만으로 단축하는 것이 목표이다.

이 목표에 가까워지기 위해 세 가지 주요 기능을 도입했다:

  • 자바스크립트를 사용해 컴파일 시간을 줄인다.
  • es6/flow/jsx 파일을 VM이 이해할 수 있는 일반 자바스크립트로 변환하는 Packager 도구를 구현했다. 이 도구는 서버로 설계되어 중간 상태를 메모리에 유지해 빠른 증분 변경을 가능하게 하고, 멀티코어를 활용한다.
  • 파일 저장 시 앱을 다시 로드하는 Live Reload 기능을 구축했다.

현재 개발자들의 병목 현상은 앱을 다시 로드하는 시간이 아니라 앱 상태를 잃는 문제이다. 일반적인 시나리오는 런치 스크린에서 여러 화면을 거쳐야 하는 기능을 작업하는 경우이다. 매번 다시 로드할 때마다 동일한 경로를 반복해 클릭해야 하기 때문에 피드백 루프가 몇 초씩 길어지게 된다.

핫 리로딩

핫 리로딩의 핵심 개념은 앱을 계속 실행한 상태에서 수정한 파일의 새 버전을 런타임에 주입하는 것이다. 이 방식을 사용하면 앱의 상태를 잃지 않으면서 UI를 조정할 수 있다.

동영상 하나가 수많은 설명보다 효과적이다. 라이브 리로딩(기존 방식)과 핫 리로딩(새 방식)의 차이를 확인해 보자.

자세히 보면, 빨간색 박스에서 복구가 가능하고, 이전에 없던 모듈을 가져오더라도 전체 리로드 없이 시작할 수 있다는 것을 알 수 있다.

주의 사항: 자바스크립트는 상태를 많이 사용하는 언어이기 때문에 핫 리로딩을 완벽하게 구현할 수 없다. 실제로 현재 설정은 대부분의 일반적인 사용 사례에서 잘 작동하며, 문제가 발생할 경우 언제든지 전체 리로드를 사용할 수 있다.

핫 리로딩은 0.22 버전부터 사용 가능하며, 다음과 같이 활성화할 수 있다:

  • 개발자 메뉴를 연다.
  • "핫 리로딩 활성화"를 탭한다.

핵심 구현 과정

이제 핫 리로딩이 왜 필요한지, 어떻게 사용하는지 알았으니 실제 동작 원리를 살펴보자.

핫 리로딩은 Hot Module Replacement(HMR)라는 기능 위에 구축되었다. 이 기능은 webpack에서 처음 도입했고, React Native Packager 내부에 구현했다. HMR은 Packager가 파일 변경을 감지하고, 앱에 포함된 간단한 HMR 런타임으로 HMR 업데이트를 전송한다.

간단히 말해, HMR 업데이트는 변경된 JS 모듈의 새 코드를 포함한다. 런타임이 이를 받으면, 이전 모듈의 코드를 새 코드로 대체한다:

HMR 업데이트는 단순히 변경할 모듈의 코드만 포함하지 않는다. 코드를 교체하는 것만으로는 런타임이 변경 사항을 반영하기에 충분하지 않기 때문이다. 문제는 모듈 시스템이 이미 업데이트하려는 모듈의 _exports_를 캐시했을 가능성이다. 예를 들어, 다음과 같은 두 모듈로 구성된 앱이 있다고 가정하자:

// log.js
function log(message) {
const time = require('./time');
console.log(`[${time()}] ${message}`);
}

module.exports = log;
// time.js
function time() {
return new Date().getTime();
}

module.exports = time;

log 모듈은 time 모듈이 제공하는 현재 날짜를 포함해 메시지를 출력한다.

앱이 번들링될 때, React Native는 __d 함수를 사용해 각 모듈을 모듈 시스템에 등록한다. 이 앱의 경우, 여러 __d 정의 중 log에 대한 정의가 하나 있을 것이다:

__d('log', function() {
... // 모듈의 코드
});

이 호출은 각 모듈의 코드를 익명 함수로 감싸며, 이를 일반적으로 팩토리 함수라고 부른다. 모듈 시스템 런타임은 각 모듈의 팩토리 함수, 이미 실행되었는지 여부, 그리고 실행 결과(exports)를 추적한다. 모듈이 필요할 때, 모듈 시스템은 이미 캐시된 exports를 제공하거나 모듈의 팩토리 함수를 처음 실행하고 결과를 저장한다.

앱을 시작하고 log를 요청한다고 가정하자. 이 시점에서 logtime의 팩토리 함수는 아직 실행되지 않았으므로 exports가 캐시되지 않았다. 그런 다음 사용자가 time을 수정해 날짜를 MM/DD 형식으로 반환하도록 변경한다:

// time.js
function bar() {
const date = new Date();
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
function log(message) {
console.log(`[${time()}] ${message}`);
}

module.exports = log;

log가 요청되면, 런타임은 logtime의 exports를 캐시한다(1단계). 그런 다음 time이 수정되면, HMR 프로세스는 단순히 time의 코드를 교체하는 것으로 끝날 수 없다. 그렇게 하면 log가 실행될 때 time의 캐시된 복사본(이전 코드)을 사용하게 된다.

logtime의 변경 사항을 반영하려면, log의 캐시된 exports를 지워야 한다. 왜냐하면 log가 의존하는 모듈 중 하나가 핫 스왑되었기 때문이다(3단계). 마지막으로 log가 다시 요청되면, 팩토리 함수가 실행되면서 time을 요청하고 새 코드를 가져온다.

HMR API

React Native의 HMR(Hot Module Replacement)은 hot 객체를 도입해 모듈 시스템을 확장한다. 이 API는 webpack의 HMR API를 기반으로 한다. hot 객체는 accept라는 함수를 제공하는데, 이 함수를 사용해 모듈이 핫 스왑될 때 실행될 콜백을 정의할 수 있다. 예를 들어, time의 코드를 다음과 같이 변경하면, time을 저장할 때마다 콘솔에 "time changed"가 출력된다:

// time.js
function time() {
... // 새로운 코드
}

module.hot.accept(() => {
console.log('time changed');
});

module.exports = time;

이 API를 직접 사용해야 하는 경우는 드물다. 대부분의 일반적인 사용 사례에서는 핫 리로딩이 기본적으로 동작한다.

HMR 런타임

앞서 살펴봤듯이, HMR 업데이트를 단순히 수락하는 것만으로는 충분하지 않다. 이미 실행된 모듈이 핫 스왑된 모듈을 사용하고 있을 수 있고, 해당 모듈의 임포트가 캐시된 상태일 수 있다. 예를 들어, 영화 앱 예제의 의존성 트리에서 최상위에 MovieRouter가 있고, 이 모듈이 MovieSearchMovieScreen 뷰에 의존하며, 이 뷰들은 이전 예제의 logtime 모듈에 의존한다고 가정해 보자.

사용자가 영화 검색 뷰에 접근했지만 다른 뷰에는 접근하지 않았다면, MovieScreen을 제외한 모든 모듈의 익스포트가 캐시된다. time 모듈에 변경이 발생하면, 런타임은 log의 익스포트를 지워 time의 변경 사항을 반영해야 한다. 이 프로세스는 여기서 끝나지 않는다. 런타임은 모든 부모 모듈이 업데이트를 수락할 때까지 이 과정을 재귀적으로 반복한다. 따라서 log에 의존하는 모듈들을 가져와 업데이트를 시도한다. MovieScreen의 경우 아직 필요하지 않았기 때문에 중단할 수 있다. MovieSearch의 경우 익스포트를 지우고 부모 모듈을 재귀적으로 처리해야 한다. 마지막으로 MovieRouter에 대해 동일한 작업을 수행하고, 더 이상 의존하는 모듈이 없으므로 프로세스를 종료한다.

런타임은 의존성 트리를 탐색하기 위해 HMR 업데이트 시 Packager로부터 역의존성 트리를 받는다. 이 예제에서 런타임은 다음과 같은 JSON 객체를 받게 된다:

{
"modules": [
{
"name": "time",
"code": /* time's new code */
}
],
"inverseDependencies": {
"MovieRouter": [],
"MovieScreen": ["MovieRouter"],
"MovieSearch": ["MovieRouter"],
"log": ["MovieScreen", "MovieSearch"],
"time": ["log"]
}
}

리액트 컴포넌트

리액트 컴포넌트는 핫 리로딩과 함께 사용하기가 조금 더 까다롭다. 문제는 기존 코드를 새로운 코드로 단순히 교체할 수 없다는 점이다. 그렇게 하면 컴포넌트의 상태를 잃어버리기 때문이다. 리액트 웹 애플리케이션의 경우, Dan Abramov가 이 문제를 해결하기 위해 웹팩의 HMR API를 사용하는 바벨 트랜스폼을 구현했다. 간단히 말해, 그의 솔루션은 _트랜스폼 시점_에 모든 리액트 컴포넌트에 대한 프록시를 생성하는 방식으로 동작한다. 프록시는 컴포넌트의 상태를 유지하고, 실제 컴포넌트에 라이프사이클 메서드를 위임한다. 이 실제 컴포넌트가 핫 리로딩의 대상이 된다:

프록시 컴포넌트를 생성하는 것 외에도, 이 트랜스폼은 리액트가 컴포넌트를 리렌더링하도록 강제하는 코드를 포함한 accept 함수를 정의한다. 이 방식을 통해 앱의 상태를 잃지 않고 렌더링 코드를 핫 리로딩할 수 있다.

리액트 네이티브에 기본으로 포함된 트랜스포머babel-preset-react-native를 사용한다. 이 프리셋은 웹팩을 사용하는 리액트 웹 프로젝트와 동일한 방식으로 react-transform을 사용하도록 설정되어 있다.

Redux 스토어

Redux 스토어에서 핫 리로딩을 활성화하려면, webpack을 사용하는 웹 프로젝트에서와 유사하게 HMR API를 사용하면 된다:

// configureStore.js
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import reducer from '../reducers';

export default function configureStore(initialState) {
const store = createStore(
reducer,
initialState,
applyMiddleware(thunk),
);

if (module.hot) {
module.hot.accept(() => {
const nextRootReducer = require('../reducers/index').default;
store.replaceReducer(nextRootReducer);
});
}

return store;
};

리듀서를 변경하면, 해당 리듀서를 수락하는 코드가 클라이언트로 전송된다. 그런 다음 클라이언트는 리듀서가 스스로를 수락하는 방법을 모른다는 것을 인식하고, 해당 리듀서를 참조하는 모든 모듈을 찾아 수락하려고 시도한다. 결국, 이 흐름은 단일 스토어인 configureStore 모듈에 도달하여 HMR 업데이트를 수락하게 된다.

결론

핫 리로딩 기능을 개선하는 데 관심이 있다면, Dan Abramov가 쓴 핫 리로딩의 미래에 관한 글을 읽고 기여해 보길 권한다. 예를 들어, Johny Days는 여러 연결된 클라이언트와 함께 작동하도록 만드는 작업을 진행 중이다. 이 기능을 유지하고 발전시키는 데 여러분의 도움이 필요하다.

React Native를 통해 우리는 더 나은 개발자 경험을 제공하기 위해 앱을 만드는 방식을 재고할 수 있다. 핫 리로딩은 퍼즐의 한 조각에 불과하다. 더 나은 경험을 만들기 위해 어떤 창의적인 해결책을 적용할 수 있을까?

React Native 앱 접근성 구현하기

· 2 min read
Georgiy Kassabli
Facebook 소프트웨어 엔지니어

웹에서 React, 모바일에서 React Native가 출시되면서 개발자들이 제품을 구축할 수 있는 새로운 프론트엔드 프레임워크를 제공했다. 견고한 제품을 만들기 위한 핵심 요소 중 하나는 시각 장애가 있거나 다른 장애를 가진 사람들을 포함해 누구나 사용할 수 있도록 보장하는 것이다. React와 React Native의 접근성 API를 사용하면 시각 장애인을 위한 스크린 리더 같은 보조 기술을 사용하는 사람들도 React 기반 경험을 활용할 수 있다.

이 글에서는 React Native 앱에 초점을 맞춘다. React 접근성 API는 Android와 iOS API와 유사하게 디자인했다. Android, iOS 또는 웹을 위해 접근성 있는 애플리케이션을 개발해본 경험이 있다면 React AX API의 프레임워크와 용어에 익숙할 것이다. 예를 들어, UI 엘리먼트를 _accessible_로 설정해(따라서 보조 기술에 노출) 엘리먼트에 대한 문자열 설명을 제공하기 위해 _accessibilityLabel_을 사용할 수 있다:

<View accessible={true} accessibilityLabel="This is simple view">

Facebook의 React 기반 제품 중 하나인 Ads Manager 앱을 살펴보며 React AX API를 조금 더 깊이 있게 적용하는 방법을 알아보자.

이 글은 발췌문이다. Facebook Code에서 나머지 내용을 읽어보자.

React Native for Android: 첫 크로스 플랫폼 React Native 앱을 만든 방법

· 2 min read
Facebook 소프트웨어 엔지니어

올해 초, 우리는 iOS용 React Native를 소개했다. React Native는 웹에서 React를 사용하던 개발자들에게 익숙한 선언적이고 독립적인 UI 컴포넌트와 빠른 개발 사이클을 모바일 플랫폼에 가져오면서도, 네이티브 애플리케이션의 속도, 정확성, 그리고 느낌을 유지한다. 오늘, 우리는 Android용 React Native를 출시하게 되어 기쁘게 생각한다.

Facebook에서는 이미 1년 넘게 React Native를 프로덕션 환경에서 사용해 왔다. 거의 정확히 1년 전, 우리 팀은 Ads Manager 앱을 개발하기 시작했다. 우리의 목표는 Facebook에서 광고를 하는 수백만 명의 사람들이 계정을 관리하고 이동 중에도 새로운 광고를 생성할 수 있는 새로운 앱을 만드는 것이었다. 이 앱은 Facebook의 첫 번째 완전한 React Native 앱이자 첫 번째 크로스 플랫폼 앱이 되었다. 이 글에서 우리는 이 앱을 어떻게 구축했는지, React Native가 어떻게 더 빠르게 개발할 수 있게 했는지, 그리고 우리가 배운 교훈을 공유하고자 한다.

이 글은 발췌문이다. 전체 글은 Facebook Code에서 확인할 수 있다.