Skip to main content

State of React Native 2018

· 10 min read
Sophie Alpert
Engineering Manager on React at Facebook

React Native의 최신 상태에 대해 업데이트를 공유한 지 꽤 시간이 지났다.

Facebook에서는 이전보다 더 많은 프로젝트에서 React Native를 사용하고 있다. 가장 인기 있는 제품 중 하나는 매월 8억 명이 사용하는 앱의 최상위 탭 중 하나인 Marketplace다. 2015년에 생성된 이후, Marketplace는 앱의 다양한 부분에 걸쳐 100개가 넘는 전체 화면 뷰를 포함해 모두 React Native로 구축되었다.

또한 앱의 새로운 부분에서도 React Native를 활용하고 있다. 지난달 F8 키노트를 본 사람이라면 Blood Donations, Crisis Response, Privacy Shortcuts, Wellness Checks 등을 기억할 것이다. 이 모든 최신 기능이 React Native로 만들어졌다. 메인 Facebook 앱 외부 프로젝트에서도 React Native를 사용한다. 새로운 Oculus Go VR 헤드셋에는 React Native로 완전히 구축된 컴패니언 모바일 앱이 포함되어 있으며, 헤드셋 자체에서도 React VR이 다양한 경험을 제공한다.

당연히 앱을 구축하기 위해 다른 기술들도 많이 사용한다. LithoComponentKit는 앱에서 광범위하게 사용하는 두 라이브러리다. 둘 다 네이티브 화면을 구축하기 위해 React와 유사한 컴포넌트 API를 제공한다. React Native가 다른 모든 기술을 대체하는 것이 목표는 아니었다. 우리는 React Native 자체를 개선하는 데 집중하지만, 다른 팀들이 React Native의 아이디어를 차용해 즉시 리로드와 같은 기능을 비자바스크립트 코드에도 적용하는 것을 보는 것을 좋아한다.

아키텍처

2013년 React Native 프로젝트를 시작할 때, 우리는 JavaScript와 네이티브 사이에 단일 "브릿지"를 설계했다. 이 브릿지는 비동기적이고 직렬화 가능하며 일괄 처리되는 특징을 가졌다. React DOM이 React 상태 업데이트를 document.createElement(attrs).appendChild()와 같은 DOM API에 대한 명령형 호출로 변환하는 것처럼, React Native는 [["createView", attrs], ["manageChildren", ...]]과 같이 수행할 뮤테이션 목록을 담은 단일 JSON 메시지를 반환하도록 설계했다. 우리는 이 시스템이 동기 응답을 받는 데 의존하지 않도록 설계했고, 목록에 있는 모든 것이 JSON으로 완전히 직렬화될 수 있도록 했다. 이는 우리에게 유연성을 제공하기 위한 선택이었다. 이 아키텍처 위에서, WebSocket 연결을 통해 모든 JavaScript 코드를 비동기적으로 실행하는 Chrome 디버깅과 같은 도구를 구축할 수 있었다.

지난 5년 동안, 이러한 초기 설계 원칙이 일부 기능 구현을 어렵게 만든다는 사실을 발견했다. 비동기 브릿지는 JavaScript 로직을 동기 응답을 기대하는 많은 네이티브 API와 직접 통합할 수 없게 한다. 네이티브 호출을 큐에 넣는 일괄 처리 브릿지는 React Native 앱이 네이티브로 구현된 함수를 호출하기 어렵게 만든다. 직렬화 가능한 브릿지는 두 세계 사이에서 메모리를 직접 공유하는 대신 불필요한 복사를 유발한다. React Native로 완전히 구축된 앱의 경우, 이러한 제약은 대체로 감내할 만하다. 하지만 React Native와 기존 앱 코드 간의 복잡한 통합이 필요한 앱에서는 불편함을 초래한다.

우리는 React Native를 대규모로 재구성하여 프레임워크를 더 유연하게 만들고, 하이브리드 JavaScript/네이티브 앱에서 네이티브 인프라와 더 잘 통합할 수 있도록 하고 있다. 이 프로젝트를 통해 지난 5년간 배운 교훈을 적용하고, 점진적으로 더 현대적인 아키텍처로 전환할 계획이다. React Native의 내부를 대부분 재작성하고 있지만, 대부분의 변경 사항은 내부적으로만 이루어진다. 기존 React Native 앱은 거의 또는 전혀 변경 없이 계속 작동할 것이다.

React Native를 더 가볍게 만들고 기존 네이티브 앱에 더 잘 통합하기 위해, 이 재구성은 세 가지 주요 내부 변경을 포함한다. 첫째, 스레딩 모델을 변경한다. 각 UI 업데이트가 세 개의 다른 스레드에서 작업을 수행해야 하는 대신, 높은 우선순위 업데이트를 위해 모든 스레드에서 JavaScript를 동기적으로 호출할 수 있게 하면서도, 낮은 우선순위 작업은 메인 스레드에서 벗어나 반응성을 유지할 수 있도록 한다. 둘째, 비동기 렌더링 기능을 React Native에 통합하여 여러 렌더링 우선순위를 허용하고 비동기 데이터 처리를 단순화한다. 마지막으로, 브릿지를 더 빠르고 가볍게 단순화한다. 네이티브와 JavaScript 간의 직접 호출은 더 효율적이며, 크로스 언어 스택 트레이스와 같은 디버깅 도구를 더 쉽게 구축할 수 있게 한다.

이러한 변경이 완료되면, 더 긴밀한 통합이 가능해진다. 현재는 복잡한 해킹 없이는 네이티브 네비게이션 및 제스처 처리나 UICollectionView, RecyclerView와 같은 네이티브 컴포넌트를 통합할 수 없다. 스레딩 모델 변경 후에는 이러한 기능을 쉽게 구축할 수 있을 것이다.

이 작업이 완료에 가까워지면 올해 안에 더 자세한 내용을 공개할 예정이다.

커뮤니티

페이스북 내부 커뮤니티와 함께, 우리는 페이스북 외부에서도 활발한 React Native 사용자와 기여자들을 보유하고 있다는 점을 자랑스럽게 생각한다. 우리는 React Native 사용자에게 더 나은 서비스를 제공하고, 프로젝트에 기여하기 쉽게 만드는 방식으로 React Native 커뮤니티를 더욱 지원하고 싶다.

우리의 아키텍처 변경이 React Native가 다른 네이티브 인프라와 더 깔끔하게 상호작용하도록 돕는 것처럼, React Native는 JavaScript 생태계에 더 잘 맞도록 JavaScript 측면에서 더 가벼워져야 한다. 이는 VM과 번들러를 교체 가능하게 만드는 것을 포함한다. 우리는 주요 변경 사항의 속도가 따라가기 어려울 수 있다는 것을 알고 있으므로, 주요 릴리즈를 줄이는 방법을 모색하고 싶다. 마지막으로, 일부 팀이 시작 최적화와 같은 주제에서 더 철저한 문서를 원한다는 것을 알고 있으며, 우리의 전문 지식이 아직 문서화되지 않은 부분이 있다. 이러한 변화들이 올해 중에 이루어질 것으로 기대해도 좋다.

React Native를 사용한다면, 여러분은 우리 커뮤니티의 일원이다. React Native를 더 나은 도구로 만들기 위해 우리에게 계속 의견을 알려주길 바란다.

React Native는 모바일 개발자의 도구 상자 중 하나일 뿐이지만, 우리는 이 도구를 강력히 믿고 있다. 그리고 우리는 지난 한 해 동안 500명 이상의 기여자가 2500개 이상의 커밋을 통해 매일 더 나은 도구로 만들고 있다.

React Native에서 TypeScript 사용하기

· 13 min read
Ash Furrow
Artsy 소프트웨어 엔지니어

JavaScript! 모두가 좋아하는 언어다. 하지만 어떤 이들은 타입도 좋아한다. 다행히 JavaScript에 강력한 타입을 추가할 수 있는 옵션이 있다. 필자는 TypeScript를 선호하지만, React Native는 기본적으로 Flow를 지원한다. 어떤 것을 선택할지는 취향에 달렸다. 각각 JavaScript에 타입을 추가하는 방식이 다르기 때문이다. 오늘은 React Native 앱에서 TypeScript를 사용하는 방법을 살펴본다.

이 글은 Microsoft의 TypeScript-React-Native-Starter 저장소를 가이드로 삼는다.

업데이트: 이 블로그 글이 작성된 이후로 상황이 더 간단해졌다. 이 글에서 설명하는 모든 설정을 단 하나의 커맨드로 대체할 수 있다:

npx react-native init MyAwesomeProject --template react-native-template-typescript

하지만 Babel의 TypeScript 지원에는 몇 가지 제한 사항이 있다. 위 블로그 글에서 자세히 다룬다. 이 글에서 설명한 단계도 여전히 유효하며, Artsy는 여전히 react-native-typescript-transformer를 프로덕션에서 사용하고 있다. 하지만 React Native와 TypeScript를 가장 빠르게 시작하려면 위 커맨드를 사용하면 된다. 나중에 필요하면 언제든지 전환할 수 있다.

어쨌든, 즐겁게 진행해보자! 원본 블로그 글은 아래에서 계속된다.

사전 준비

여러분이 다양한 플랫폼에서 개발하거나 여러 타입의 디바이스를 대상으로 작업할 수 있기 때문에 기본 설정이 복잡할 수 있다. 먼저 TypeScript 없이 일반 React Native 앱을 실행할 수 있는지 확인해야 한다. React Native 공식 웹사이트의 시작하기 가이드를 따라 기본 설정을 완료한다. 디바이스나 에뮬레이터에 앱을 배포할 수 있게 되면, TypeScript를 사용한 React Native 앱 개발을 시작할 준비가 된 것이다.

또한 Node.js, npm, 그리고 Yarn이 필요하다.

초기화

일반적인 React Native 프로젝트를 스캐폴딩해본 후, TypeScript를 추가할 준비가 되었다. 바로 시작해보자.

react-native init MyAwesomeProject
cd MyAwesomeProject

TypeScript 추가하기

다음 단계는 프로젝트에 TypeScript를 추가하는 것이다. 아래 명령어들은 다음과 같은 작업을 수행한다:

  • 프로젝트에 TypeScript를 추가한다.
  • 프로젝트에 React Native TypeScript Transformer를 추가한다.
  • 빈 TypeScript 설정 파일을 초기화한다. 이 파일은 다음 단계에서 설정할 것이다.
  • 빈 React Native TypeScript Transformer 설정 파일을 추가한다. 이 파일도 다음 단계에서 설정할 것이다.
  • React와 React Native를 위한 타입 정의를 추가한다.

이제 아래 명령어를 실행해 보자.

yarn add --dev typescript
yarn add --dev react-native-typescript-transformer
yarn tsc --init --pretty --jsx react
touch rn-cli.config.js
yarn add --dev @types/react @types/react-native

tsconfig.json 파일은 TypeScript 컴파일러의 모든 설정을 포함한다. 위 명령어로 생성된 기본값은 대부분 적합하지만, 파일을 열고 다음 줄의 주석을 해제한다.

{
/* 설정 파일에서 다음 줄을 찾아 주석을 해제한다. */
// "allowSyntheticDefaultImports": true, /* 기본 내보내기가 없는 모듈에서 기본 import를 허용한다. 이 설정은 코드 생성에는 영향을 미치지 않고, 타입 검사에만 영향을 준다. */
}

rn-cli.config.js 파일은 React Native TypeScript Transformer의 설정을 포함한다. 파일을 열고 다음 내용을 추가한다.

module.exports = {
getTransformModulePath() {
return require.resolve('react-native-typescript-transformer');
},
getSourceExts() {
return ['ts', 'tsx'];
},
};

타입스크립트로 마이그레이션

생성된 App.js__tests_/App.js 파일을 App.tsx로 이름을 변경한다. index.js.js 확장자를 그대로 유지한다. 모든 새로운 파일은 .tsx 확장자를 사용한다. (JSX를 포함하지 않는 파일은 .ts 확장자를 사용한다.)

이 상태에서 앱을 실행하면 object prototype may only be an object or null과 같은 오류가 발생할 수 있다. 이 오류는 React의 기본 내보내기와 명명된 내보내기를 동일한 줄에서 가져오지 못해 발생한다. App.tsx 파일을 열고 상단의 import 문을 다음과 같이 수정한다:

-import React, { Component } from 'react';
+import React from 'react'
+import { Component } from 'react';

이 문제는 Babel과 타입스크립트가 CommonJS 모듈을 처리하는 방식의 차이에서 비롯된다. 향후 두 도구는 동일한 동작 방식으로 안정화될 것이다.

이제 React Native 앱을 실행할 수 있어야 한다.

타입스크립트 테스트 환경 설정

React Native는 기본적으로 Jest를 제공한다. 따라서 React Native 앱을 타입스크립트로 테스트하려면 devDependenciests-jest를 추가해야 한다.

yarn add --dev ts-jest

그런 다음 package.json 파일을 열고 jest 필드를 다음과 같이 교체한다:

{
"jest": {
"preset": "react-native",
"moduleFileExtensions": [
"ts",
"tsx",
"js"
],
"transform": {
"^.+\\.(js)$": "<rootDir>/node_modules/babel-jest",
"\\.(ts|tsx)$": "<rootDir>/node_modules/ts-jest/preprocessor.js"
},
"testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$",
"testPathIgnorePatterns": [
"\\.snap$",
"<rootDir>/node_modules/"
],
"cacheDirectory": ".jest/cache"
}
}

이 설정은 Jest가 .ts.tsx 파일을 ts-jest로 실행하도록 구성한다.

의존성 타입 선언 설치하기

TypeScript에서 최상의 경험을 얻으려면 타입 체커가 의존성의 구조와 API를 이해할 수 있도록 해야 한다. 일부 라이브러리는 .d.ts 파일(타입 선언/타입 정의 파일)을 패키지와 함께 배포한다. 이 파일은 기본 JavaScript의 구조를 설명한다. 다른 라이브러리의 경우, @types/ npm 범위에서 적절한 패키지를 명시적으로 설치해야 한다.

예를 들어, Jest, React, React Native, React Test Renderer에 대한 타입이 필요하다.

yarn add --dev @types/jest @types/react @types/react-native @types/react-test-renderer

이 선언 파일 패키지를 dev 의존성으로 저장했다. 이는 React Native 이 런타임이 아닌 개발 중에만 이 의존성을 사용하기 때문이다. NPM에 라이브러리를 배포한다면, 일부 타입 의존성을 일반 의존성으로 추가해야 할 수도 있다.

.d.ts 파일에 대해 더 자세히 알아보려면 여기를 참고한다.

더 많은 파일 무시하기

소스 컨트롤을 위해 .jest 폴더를 무시하고 싶을 것이다. git을 사용한다면 .gitignore 파일에 항목을 추가하면 된다.

# Jest
#
.jest/

체크포인트로, 파일을 버전 컨트롤에 커밋하는 것을 고려해 보자.

git init
git add .gitignore # 파일을 무시하기 위해 먼저 이 작업을 수행하는 것이 중요하다
git add .
git commit -am "Initial commit."

컴포넌트 추가하기

앱에 새로운 컴포넌트를 추가해 보자. Hello.tsx 컴포넌트를 만들어 보자. 이 컴포넌트는 실제 앱에서 사용할 만한 것은 아니지만, React Native에서 TypeScript를 어떻게 사용하는지 보여주는 교육용 예제이다.

components 디렉토리를 만들고 다음 예제를 추가해 보자.

// components/Hello.tsx
import React from 'react';
import {Button, StyleSheet, Text, View} from 'react-native';

export interface Props {
name: string;
enthusiasmLevel?: number;
}

interface State {
enthusiasmLevel: number;
}

export class Hello extends React.Component<Props, State> {
constructor(props: Props) {
super(props);

if ((props.enthusiasmLevel || 0) <= 0) {
throw new Error(
'조금 더 열정을 보여주세요. :D',
);
}

this.state = {
enthusiasmLevel: props.enthusiasmLevel || 1,
};
}

onIncrement = () =>
this.setState({
enthusiasmLevel: this.state.enthusiasmLevel + 1,
});
onDecrement = () =>
this.setState({
enthusiasmLevel: this.state.enthusiasmLevel - 1,
});
getExclamationMarks = (numChars: number) =>
Array(numChars + 1).join('!');

render() {
return (
<View style={styles.root}>
<Text style={styles.greeting}>
Hello{' '}
{this.props.name +
this.getExclamationMarks(this.state.enthusiasmLevel)}
</Text>

<View style={styles.buttons}>
<View style={styles.button}>
<Button
title="-"
onPress={this.onDecrement}
accessibilityLabel="감소"
color="red"
/>
</View>

<View style={styles.button}>
<Button
title="+"
onPress={this.onIncrement}
accessibilityLabel="증가"
color="blue"
/>
</View>
</View>
</View>
);
}
}

// 스타일
const styles = StyleSheet.create({
root: {
alignItems: 'center',
alignSelf: 'center',
},
buttons: {
flexDirection: 'row',
minHeight: 70,
alignItems: 'stretch',
alignSelf: 'center',
borderWidth: 5,
},
button: {
flex: 1,
paddingVertical: 0,
},
greeting: {
color: '#999',
fontWeight: 'bold',
},
});

이 코드는 상당히 길지만, 주요 부분을 살펴보자:

  • HTML 엘리먼트인 div, span, h1 등을 렌더링하는 대신, ViewButton 같은 네이티브 컴포넌트를 사용한다. 이 컴포넌트들은 다양한 플랫폼에서 동작한다.
  • 스타일은 React Native가 제공하는 StyleSheet.create 함수를 사용해 지정한다. React의 스타일시트는 플렉스 박스를 통해 레이아웃을 제어하고, CSS와 유사한 구문으로 스타일을 적용할 수 있다.

컴포넌트 테스트 추가하기

이제 컴포넌트를 만들었으니 테스트를 작성해 보자.

이미 테스트 러너로 Jest를 설치했다. 컴포넌트에 대한 스냅샷 테스트를 작성할 것이므로, 필요한 애드온을 추가한다:

yarn add --dev react-addons-test-utils

이제 components 디렉토리에 __tests__ 폴더를 만들고 Hello.tsx에 대한 테스트를 추가한다:

// components/__tests__/Hello.tsx
import React from 'react';
import renderer from 'react-test-renderer';

import {Hello} from '../Hello';

it('기본값으로 정상 렌더링된다', () => {
const button = renderer
.create(<Hello name="World" enthusiasmLevel={1} />)
.toJSON();
expect(button).toMatchSnapshot();
});

테스트를 처음 실행하면 렌더링된 컴포넌트의 스냅샷을 생성하고 components/__tests__/__snapshots__/Hello.tsx.snap 파일에 저장한다. 컴포넌트를 수정할 때는 스냅샷을 업데이트하고 의도치 않은 변경 사항을 검토해야 한다. React Native 컴포넌트 테스트에 대해 더 자세히 알고 싶다면 여기를 참고한다.

다음 단계

공식 React 튜토리얼과 상태 관리 라이브러리 Redux를 확인해 보자. 이 리소스들은 React Native 앱을 개발할 때 유용하다. 또한 ReactXP도 살펴볼 만하다. ReactXP는 TypeScript로 작성된 컴포넌트 라이브러리로, 웹용 React와 React Native를 모두 지원한다.

더 안전한 타입 시스템을 활용한 React Native 개발 환경에서 즐거운 코딩을 해보자!

React Native로 제작된 Build.com 앱

· 10 min read
Garrett McCullough
시니어 모바일 엔지니어

캘리포니아 치코에 본사를 둔 Build.com은 홈 인테리어 제품을 판매하는 최대 규모의 온라인 리테일러 중 하나다. 이 팀은 18년 동안 웹 중심의 강력한 비즈니스를 운영해 왔으며, 2015년 모바일 앱 개발을 고민하기 시작했다. 당시 작은 팀 규모와 제한된 네이티브 개발 경험 때문에 독자적인 안드로이드와 iOS 앱을 만드는 것은 현실적이지 않았다. 대신, 매우 새로운 React Native 프레임워크를 도입하기로 결정했다. 2015년 8월 12일 React Native v0.8.0으로 첫 커밋을 진행했고, 2016년 10월 15일 양대 앱 스토어에 앱을 출시했다. 지난 2년 동안 앱을 지속적으로 업그레이드하고 확장해 왔으며, 현재는 React Native 버전 0.53.0을 사용하고 있다.

앱은 https://www.build.com/app에서 확인할 수 있다.

주요 기능

이 앱은 일반적인 이커머스 앱에서 기대할 수 있는 모든 기능을 제공한다. 상품 목록, 검색 및 정렬 기능, 복잡한 상품 설정 기능, 찜하기 등이 포함된다. 표준 신용카드 결제는 물론 PayPal, iOS 사용자를 위한 Apple Pay도 지원한다.

특히 눈여겨볼 만한 몇 가지 독특한 기능은 다음과 같다:

  1. 약 40개 상품에 대해 90가지 마감재를 적용한 3D 모델 제공
  2. 증강현실(AR) 기능을 통해 조명과 수도꼭지가 실제 집 안에서 어떻게 보일지 98% 정확도로 확인 가능. Build.com React Native 앱은 Apple 앱스토어에서 AR 쇼핑으로 소개됨. 현재 안드로이드와 iOS 모두에서 AR 기능 사용 가능
  3. 프로젝트 단계별 쇼핑 리스트를 작성하고 선택 과정에서 협업할 수 있는 프로젝트 관리 기능

앞으로도 AR을 활용한 몰입형 쇼핑의 다음 단계를 비롯해, 앱 사용 경험을 지속적으로 개선할 다양한 기능을 개발 중이다.

개발 워크플로우

Build.com은 각 개발자가 자신에게 가장 적합한 도구를 선택할 수 있도록 한다.

  • 사용하는 IDE로는 Atom, IntelliJ, VS Code, Sublime, Eclipse 등이 있다.
  • 유닛 테스트의 경우, 개발자는 새 컴포넌트에 대한 Jest 유닛 테스트를 작성해야 한다. 또한 jest-coverage-ratchet을 사용해 앱의 오래된 부분에 대한 테스트 커버리지를 높이기 위해 노력하고 있다.
  • 베타 버전과 릴리스 후보를 빌드하기 위해 Jenkins를 사용한다. 이 프로세스는 잘 작동하지만, 릴리스 노트와 다른 산출물을 만드는 데 상당한 작업이 필요하다.
  • 통합 테스트는 데스크톱, 모바일, 웹을 아우르는 공유 테스터 풀로 구성된다. 자동화 엔지니어는 Java와 Appium을 사용해 자동화된 통합 테스트 스위트를 구축 중이다.
  • 워크플로우의 다른 부분에는 상세한 eslint 설정, 테스트에 필요한 속성을 강제하는 커스텀 규칙, 문제가 있는 변경 사항을 차단하는 pre-push 훅 등이 포함된다.

앱에서 사용하는 라이브러리

Build.com 앱은 Redux, Moment, Numeral, Enzyme과 여러 React Native 브릿지 모듈을 포함한 다양한 오픈소스 라이브러리를 사용한다. 또한, 일부 포크한 오픈소스 라이브러리도 활용한다. 이 라이브러리들은 개발이 중단되었거나 커스텀 기능이 필요해 포크한 것이다. 간단히 살펴보면 약 115개의 JavaScript 및 네이티브 종속성이 있다. 현재 사용하지 않는 라이브러리를 제거할 수 있는 도구를 탐색 중이다.

현재 TypeScript를 통해 정적 타이핑을 추가하고 있으며, 옵셔널 체이닝도 도입을 고려 중이다. 이러한 기능은 여전히 발생하는 몇 가지 버그를 해결하는 데 도움이 될 것이다:

  • 잘못된 타입의 데이터
  • 예상과 달리 객체가 특정 값을 포함하지 않아 발생하는 undefined 데이터

오픈소스 기여

우리는 오픈소스에 크게 의존하고 있기 때문에, 팀은 커뮤니티에 기여하는 데 헌신한다. Build.com은 팀이 개발한 라이브러리를 오픈소스로 공개할 수 있도록 지원하며, 우리가 사용하는 라이브러리에 기여하도록 장려한다.

우리는 다음과 같은 React Native 라이브러리를 공개하고 유지보수해왔다:

  • react-native-polyfill
  • react-native-simple-store
  • react-native-contact-picker

또한 React과 React Native를 포함한 다양한 라이브러리에 기여했는데, 그 중 일부는 다음과 같다: react-native-schemes-manager, react-native-swipeable, react-native-gallery, react-native-view-transformer, react-native-navigation.

우리의 여정

지난 몇 년간 React Native와 생태계는 크게 성장했다. 초기에는 매 버전마다 몇 가지 버그를 수정하지만, 동시에 새로운 문제를 야기하는 경우가 많았다. 예를 들어, Android에서 원격 JS 디버깅이 몇 달 동안 작동하지 않던 시절도 있었다. 다행히 2017년부터는 훨씬 안정적으로 발전했다.

네비게이션 라이브러리

우리가 지속적으로 마주한 주요 과제 중 하나는 네비게이션 라이브러리였다. 오랜 시간 동안 Expo의 ex-nav 라이브러리를 사용했다. 이 라이브러리는 잘 작동했지만 결국 더 이상 지원되지 않게 되었다. 그러나 당시 우리는 주요 기능 개발에 집중하고 있었기 때문에 네비게이션 라이브러리를 교체할 시간적 여유가 없었다. 이 때문에 라이브러리를 포크하고 React 16과 iPhone X를 지원하도록 패치해야 했다. 결국 react-native-navigation으로 마이그레이션할 수 있었고, 이 라이브러리가 계속 지원되길 바란다.

브릿지 모듈

또 다른 큰 도전은 브릿지 모듈과 관련된 문제였다. 처음 시작했을 때, 많은 핵심 브릿지가 누락되어 있었다. 우리 팀원 중 한 명은 앱에서 안드로이드 연락처 선택기에 접근하기 위해 react-native-contact-picker를 직접 작성했다. 또한, React Native 내부의 변경 사항으로 인해 많은 브릿지가 깨지는 문제도 발생했다. 예를 들어, React Native v40에서 주요 변경 사항이 있었고, 우리가 앱을 업그레이드했을 때, 아직 업데이트되지 않은 3~4개의 라이브러리를 수정하기 위해 PR을 제출해야 했다.

앞으로의 전망

React Native가 계속 성장하면서, 우리 커뮤니티에 바라는 점은 다음과 같다:

  • 네비게이션 라이브러리를 안정화하고 개선하기
  • React Native 생태계 내 라이브러리 지원을 유지하기
  • 프로젝트에 네이티브 라이브러리와 브릿지 모듈을 추가하는 경험을 개선하기

React Native 커뮤니티의 기업과 개인들은 우리가 사용하는 도구를 개선하기 위해 자발적으로 시간과 노력을 투자해 왔다. 오픈소스에 아직 참여하지 않았다면, 사용하는 라이브러리의 코드나 문서를 개선하는 데 관심을 가져보길 바란다. 시작하는 데 도움이 될 만한 많은 자료가 있으며, 생각보다 훨씬 쉬울 수 있다!

React Native에서 <InputAccessoryView> 구축하기

· 12 min read
Peter Argany
페이스북 소프트웨어 엔지니어

배경

3년 전, React Native에서 입력 액세서리 뷰를 지원해 달라는 GitHub 이슈가 올라왔다.

그 후 몇 년 동안 수많은 '+1' 투표와 다양한 해결 방법이 제안되었지만, 이 문제와 관련해 React Native에는 구체적인 변화가 없었다. 오늘부터 iOS를 시작으로 네이티브 입력 액세서리 뷰에 접근할 수 있는 API를 공개한다. 이 API를 어떻게 만들었는지 여러분과 공유하게 되어 기쁘다.

배경

입력 액세서리 뷰란 무엇일까? Apple 개발자 문서를 살펴보면, 리스폰더가 퍼스트 리스폰더가 될 때 시스템 키보드 상단에 고정할 수 있는 커스텀 뷰라는 것을 알 수 있다. UIResponder를 상속받는 모든 객체는 .inputAccessoryView 프로퍼티를 읽기-쓰기로 재선언하고, 여기에 커스텀 뷰를 관리할 수 있다. 리스폰더 인프라는 이 뷰를 마운트하고, 시스템 키보드와 동기화를 유지한다. 드래그나 탭과 같이 키보드를 닫는 제스처는 프레임워크 레벨에서 입력 액세서리 뷰에 적용된다. 이를 통해 키보드 닫기와 상호작용이 가능한 콘텐츠를 만들 수 있으며, iMessage나 WhatsApp 같은 최상위 메시징 앱에서 필수적인 기능이다.

키보드 상단에 뷰를 고정하는 두 가지 일반적인 사용 사례가 있다. 첫 번째는 Facebook 컴포저의 배경 선택기와 같은 키보드 툴바를 만드는 것이다.

이 시나리오에서는 키보드가 텍스트 입력 필드에 포커스되고, 입력 액세서리 뷰는 추가 키보드 기능을 제공하는 데 사용된다. 이 기능은 입력 필드의 타입에 따라 달라진다. 지도 애플리케이션에서는 주소 제안이 될 수 있고, 텍스트 편집기에서는 서식 지정 도구가 될 수 있다.


이 시나리오에서 <InputAccessoryView>를 소유하는 Objective-C UIResponder는 명확해야 한다. <TextInput>이 퍼스트 리스폰더가 되면, 내부적으로 UITextView 또는 UITextField의 인스턴스가 된다.

두 번째 일반적인 시나리오는 스틱키 고정 텍스트 입력이다:

여기서 텍스트 입력은 실제로 입력 액세서리 뷰 자체의 일부이다. 이는 메시징 애플리케이션에서 일반적으로 사용되며, 이전 메시지 스레드를 스크롤하면서 메시지를 작성할 수 있다.


이 예제에서 <InputAccessoryView>를 소유하는 것은 누구일까? 다시 UITextViewUITextField가 될 수 있을까? 텍스트 입력이 입력 액세서리 뷰 안에 있으므로, 이는 순환 의존성처럼 들린다. 이 문제를 해결하는 것만으로도 별도의 블로그 포스트가 있다. 스포일러: 소유자는 일반적인 UIView 서브클래스이며, 수동으로 becomeFirstResponder를 호출한다.

API 설계

이제 <InputAccessoryView>가 무엇인지, 그리고 어떻게 사용할지 이해했다. 다음 단계는 두 가지 사용 사례에 적합하고, <TextInput>과 같은 기존 React Native 컴포넌트와 잘 작동하는 API를 설계하는 것이다.

키보드 툴바를 설계할 때 고려해야 할 사항은 다음과 같다:

  1. 일반적인 React Native 뷰 계층 구조를 <InputAccessoryView>로 끌어올릴 수 있어야 한다.
  2. 이 일반적이고 분리된 뷰 계층 구조가 터치를 받아들이고 애플리케이션 상태를 조작할 수 있어야 한다.
  3. 특정 <TextInput><InputAccessoryView>를 연결할 수 있어야 한다.
  4. 코드를 중복하지 않고 여러 텍스트 입력 필드 간에 <InputAccessoryView>를 공유할 수 있어야 한다.

첫 번째 요구사항은 React 포털과 유사한 개념을 사용해 달성할 수 있다. 이 설계에서 React Native 뷰를 응답자 인프라가 관리하는 UIView 계층 구조로 포털한다. React Native 뷰는 UIView로 렌더링되기 때문에 이 작업은 매우 간단하다. 다음과 같은 메서드를 재정의하면 된다:

- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex

그리고 모든 하위 뷰를 새로운 UIView 계층 구조로 연결한다. 두 번째 요구사항을 위해 <InputAccessoryView>에 새로운 RCTTouchHandler를 설정한다. 상태 업데이트는 일반적인 이벤트 콜백을 사용해 수행한다. 세 번째와 네 번째 요구사항을 위해 <TextInput> 컴포넌트 생성 시 네이티브 코드에서 액세서리 뷰 UIView 계층 구조를 찾기 위해 nativeID 필드를 사용한다. 이 함수는 기본 네이티브 텍스트 입력의 .inputAccessoryView 속성을 사용한다. 이를 통해 <InputAccessoryView><TextInput>을 ObjC 구현에서 효과적으로 연결한다.

스크롤 가능한 텍스트 입력(시나리오 2)을 지원하려면 몇 가지 제약 조건이 추가된다. 이 설계에서 입력 액세서리 뷰는 텍스트 입력을 하위 요소로 가지므로 nativeID를 통해 연결할 수 없다. 대신, 일반적인 오프스크린 UIView.inputAccessoryView를 네이티브 <InputAccessoryView> 계층 구조로 설정한다. 이 일반적인 UIView를 수동으로 첫 번째 응답자로 지정하면, 응답자 인프라에 의해 계층 구조가 마운트된다. 이 개념은 앞서 언급한 블로그 포스트에서 자세히 설명한다.

문제점과 해결 과정

이 API를 구축하는 과정에서 모든 것이 순조롭게 진행되지는 않았다. 여기서는 우리가 마주친 몇 가지 문제점과 그 해결 방법을 설명한다.

이 API를 구축하기 위한 초기 아이디어는 NSNotificationCenter를 사용해 UIKeyboardWill(Show/Hide/ChangeFrame) 이벤트를 감지하는 것이었다. 이 패턴은 일부 오픈소스 라이브러리와 Facebook 앱의 일부에서 사용되고 있다. 그러나 UIKeyboardDidChangeFrame 이벤트는 스와이프 동작 시 <InputAccessoryView>의 프레임을 업데이트하기에 충분히 빠르게 호출되지 않았다. 또한, 키보드 높이 변화도 이러한 이벤트로 캡처할 수 없었다. 이로 인해 다음과 같은 버그가 발생했다:

iPhone X에서는 텍스트 키보드와 이모티콘 키보드의 높이가 다르다. 키보드 이벤트를 사용해 텍스트 입력 프레임을 조작하는 대부분의 애플리케이션은 위 버그를 해결해야 했다. 우리의 해결책은 .inputAccessoryView 프로퍼티를 사용하는 것이었다. 이렇게 하면 응답자 인프라가 프레임 업데이트를 처리할 수 있다.


우리가 마주친 또 다른 까다로운 버그는 iPhone X의 홈 바를 피하는 것이었다. "Apple이 safeAreaLayoutGuide를 개발한 이유가 바로 이것인데, 이건 간단한 문제야!"라고 생각할 수도 있다. 우리도 그렇게 생각했다. 첫 번째 문제는 네이티브 <InputAccessoryView> 구현이 나타나기 직전까지 앵커링할 윈도우가 없다는 것이다. 이 문제는 -(BOOL)becomeFirstResponder를 재정의하고 레이아웃 제약을 강제하는 것으로 해결할 수 있다. 이러한 제약을 준수하면 액세서리 뷰가 위로 올라가지만, 또 다른 버그가 발생한다:

입력 액세서리 뷰는 홈 바를 성공적으로 피하지만, 이제 안전하지 않은 영역 뒤의 콘텐츠가 보인다. 이 문제는 radar에서 해결책을 찾을 수 있었다. 나는 네이티브 <InputAccessoryView> 계층을 safeAreaLayoutGuide 제약을 따르지 않는 컨테이너로 감쌌다. 네이티브 컨테이너는 안전하지 않은 영역의 콘텐츠를 덮고, <InputAccessoryView>는 안전 영역 경계 내에 머무르게 된다.


예제 사용법

다음은 <TextInput>의 상태를 초기화하는 키보드 툴바 버튼을 만드는 예제이다.

class TextInputAccessoryViewExample extends React.Component<
{},
*,
> {
constructor(props) {
super(props);
this.state = {text: 'Placeholder Text'};
}

render() {
const inputAccessoryViewID = 'inputAccessoryView1';
return (
<View>
<TextInput
style={styles.default}
inputAccessoryViewID={inputAccessoryViewID}
onChangeText={text => this.setState({text})}
value={this.state.text}
/>
<InputAccessoryView nativeID={inputAccessoryViewID}>
<View style={{backgroundColor: 'white'}}>
<Button
onPress={() =>
this.setState({text: 'Placeholder Text'})
}
title="Reset Text"
/>
</View>
</InputAccessoryView>
</View>
);
}
}

스틱키 고정 텍스트 입력의 다른 예제는 저장소에서 확인할 수 있다.

이 기능을 언제 사용할 수 있나요?

이 기능 구현에 대한 전체 커밋은 여기에서 확인할 수 있습니다. <InputAccessoryView>는 다음 v0.55.0 릴리스에서 사용 가능합니다.

즐거운 코딩 되세요 :)

React Native와 AWS 함께 사용하기

· 17 min read
Richard Threlkeld
AWS 모바일 부문 시니어 기술 제품 매니저

AWS는 클라우드 서비스 제공자로 기술 업계에서 널리 알려져 있다. 컴퓨팅, 스토리지, 데이터베이스 기술부터 완전 관리형 서버리스 서비스까지 다양한 제품을 제공한다. AWS 모바일 팀은 클라우드와 연결된 모바일 및 웹 애플리케이션을 더 안전하고 확장 가능하며 개발과 배포가 쉬워지도록 고객과 자바스크립트 생태계 구성원들과 긴밀히 협력해 왔다. 완전한 스타터 키트로 시작했지만, 최근에는 몇 가지 새로운 발전이 있었다.

이 블로그 포스트는 React와 React Native 개발자에게 흥미로운 내용을 다룬다:

  • AWS Amplify: 클라우드 서비스를 사용하는 자바스크립트 애플리케이션을 위한 선언적 라이브러리
  • AWS AppSync: 오프라인 및 실시간 기능을 갖춘 완전 관리형 GraphQL 서비스

AWS Amplify

React Native 애플리케이션은 Create React Native App이나 Expo 같은 도구를 사용해 쉽게 시작할 수 있다. 하지만 클라우드와 연결하려면 사용 사례에 맞는 인프라 서비스를 선택하는 과정이 복잡할 수 있다. 예를 들어, React Native 앱에서 사진을 업로드해야 한다고 가정해보자. 이를 사용자별로 보호해야 할까? 그렇다면 회원가입이나 로그인 프로세스가 필요할 것이다. 자체 사용자 디렉토리를 사용할지, 소셜 미디어 프로바이더를 사용할지도 고려해야 한다. 사용자가 로그인한 후 커스텀 비즈니스 로직을 포함한 API를 호출해야 할 수도 있다.

이런 문제를 해결하기 위해 AWS Amplify라는 라이브러리를 출시했다. 이 라이브러리는 AWS 특정 구현보다는 작업의 "카테고리"로 설계되었다. 예를 들어, 사용자가 회원가입하고 로그인한 후 개인 사진을 업로드해야 한다면, AuthStorage 카테고리를 애플리케이션에 추가하면 된다:

import { Auth } from 'aws-amplify';

Auth.signIn(username, password)
.then(user => console.log(user))
.catch(err => console.log(err));

Auth.confirmSignIn(user, code)
.then(data => console.log(data))
.catch(err => console.log(err));

위 코드에서 Amplify가 도와주는 일반적인 작업의 예를 볼 수 있다. 이메일이나 SMS를 사용한 다중 인증(MFA) 코드 사용이 그 예이다. 현재 지원되는 카테고리는 다음과 같다:

  • Auth: 자격 증명 자동화를 제공한다. 기본 구현은 AWS 자격 증명을 사용해 서명하고, Amazon Cognito에서 OIDC JWT 토큰을 가져온다. MFA 기능 같은 일반적인 기능도 지원한다.
  • Analytics: 한 줄의 코드로 Amazon Pinpoint에서 인증된 사용자나 비인증 사용자를 추적할 수 있다. 필요에 따라 커스텀 메트릭이나 속성으로 확장할 수 있다.
  • API: AWS Signature Version 4을 활용해 RESTful API와 안전하게 상호작용한다. 이 API 모듈은 Amazon API Gateway를 사용한 서버리스 인프라에서 유용하다.
  • Storage: Amazon S3에 콘텐츠를 업로드, 다운로드, 목록화하는 간단한 커맨드를 제공한다. 사용자별로 데이터를 공개 또는 비공개로 쉽게 그룹화할 수 있다.
  • Caching: 웹 앱과 React Native 전반에서 사용할 수 있는 LRU 캐시 인터페이스를 제공하며, 구현별 지속성을 사용한다.
  • i18n and Logging: 국제화 및 지역화 기능, 디버깅 및 로깅 기능을 제공한다.

Amplify의 장점 중 하나는 특정 프로그래밍 환경에 맞는 "모범 사례"를 설계에 반영했다는 점이다. 예를 들어, React Native 개발자와 고객과 협력하면서 발견한 문제 중 하나는 빠르게 작동시키기 위해 개발 중에 취한 단축키가 프로덕션 스택으로 이어지는 경우가 많다는 것이다. 이는 확장성이나 보안을 저해할 수 있으며, 인프라 재구축과 코드 리팩토링을 강제할 수 있다.

이를 피하기 위해 도움을 주는 한 가지 예는 AWS Lambda를 사용한 서버리스 참조 아키텍처이다. 이는 백엔드를 구축할 때 Amazon API Gateway와 AWS Lambda를 함께 사용하는 모범 사례를 보여준다. 이 패턴은 Amplify의 API 카테고리에 반영되어 있다. 이 패턴을 사용해 여러 REST 엔드포인트와 상호작용하고, 커스텀 비즈니스 로직을 위해 헤더를 Lambda 함수까지 전달할 수 있다. 또한 AWS Mobile CLI를 출시해 새로운 React Native 프로젝트나 기존 프로젝트를 이 기능으로 시작할 수 있게 했다. 시작하려면 npm을 통해 설치하고, 설정 프롬프트를 따르면 된다:

npm install --global awsmobile-cli
awsmobile configure

모바일 생태계에 특화된 또 다른 모범 사례는 비밀번호 보안이다. 기본 Auth 카테고리 구현은 Amazon Cognito 사용자 풀을 사용해 사용자 등록과 로그인을 처리한다. 이 서비스는 Secure Remote Password 프로토콜을 구현해 인증 시도 중 사용자를 보호한다. 프로토콜의 수학적 원리를 읽어보면, 원시 루트를 통해 그룹을 생성할 때 큰 소수를 사용해야 한다는 것을 알 수 있다. React Native 환경에서는 JIT가 비활성화되어 있어, 이런 보안 작업을 위한 BigInteger 계산이 덜 효율적이다. 이를 고려해 Android와 iOS용 네이티브 브릿지를 출시했으며, 프로젝트 내에서 연결할 수 있다:

npm install --save aws-amplify-react-native
react-native link amazon-cognito-identity-js

또한 Expo 팀이 최신 SDK에 이를 포함시켜 Amplify를 사용할 수 있게 되어, eject 없이도 사용할 수 있다.

마지막으로, React Native(및 React) 개발에 특화된 Amplify는 고차 컴포넌트(HOCs)를 포함해 앱에 로그인이나 회원가입 기능을 쉽게 추가할 수 있다:

import Amplify, { withAuthenticator } from 'aws-amplify-react-native';
import aws_exports from './aws-exports';

Amplify.configure(aws_exports);

class App extends React.Component {
...
}

export default withAuthenticator(App);

기본 컴포넌트는 <Authenticator />로도 제공되며, UI를 완전히 커스터마이즈할 수 있다. 또한 사용자 상태를 관리하는 데 필요한 속성(예: 로그인 여부나 MFA 확인 대기 상태)과 상태 변경 시 실행할 콜백도 제공한다.

마찬가지로, 다양한 사용 사례에 사용할 수 있는 일반 React 컴포넌트도 제공한다. 필요에 따라 커스터마이즈할 수 있으며, 예를 들어 Storage 모듈에서 Amazon S3의 모든 비공개 이미지를 표시할 수 있다:

<S3Album
level="private"
path={path}
filter={(item) => /jpg/i.test(item.path)}/>

앞서 보여준 것처럼, 공개 또는 비공개 스토리지 옵션을 통해 컴포넌트 기능을 제어할 수 있다. 특정 UI 컴포넌트와 상호작용할 때 자동으로 분석 데이터를 수집하는 기능도 있다:

return <S3Album track/>

AWS Amplify는 설정보다 규약을 우선하는 개발 스타일을 선호하며, 전역 초기화 루틴이나 카테고리 수준에서 초기화를 수행한다. 시작하는 가장 빠른 방법은 aws-exports 파일을 사용하는 것이다. 하지만 개발자는 기존 리소스와 함께 독립적으로 라이브러리를 사용할 수도 있다.

철학에 대한 깊은 이해와 전체 데모를 보려면 AWS re:Invent의 비디오를 확인해보자.

AWS AppSync

AWS Amplify 출시 직후, AWS AppSync도 공개했다. AWS AppSync는 오프라인과 실시간 기능을 모두 지원하는 완전 관리형 GraphQL 서비스다. 다양한 클라이언트 프로그래밍 언어(네이티브 Android 및 iOS 포함)에서 GraphQL을 사용할 수 있지만, 특히 React Native 개발자들 사이에서 인기가 높다. 이는 데이터 모델이 단방향 데이터 흐름과 컴포넌트 계층 구조에 잘 맞기 때문이다.

AWS AppSync를 사용하면 자신의 AWS 계정에 있는 리소스에 연결할 수 있다. 즉, 데이터를 소유하고 제어할 수 있다. 이는 데이터 소스를 사용해 이루어지며, Amazon DynamoDB, Amazon Elasticsearch, AWS Lambda를 지원한다. 이를 통해 NoSQL과 전체 텍스트 검색과 같은 기능을 단일 GraphQL API로 결합할 수 있다. 데이터 소스를 자유롭게 조합할 수 있다. AppSync 서비스는 스키마에서 프로비저닝할 수도 있다. 따라서 AWS 서비스에 익숙하지 않더라도 GraphQL SDL을 작성하고 버튼을 클릭하기만 하면 자동으로 실행할 수 있다.

AWS AppSync의 실시간 기능은 잘 알려진 이벤트 기반 패턴을 사용한 GraphQL 구독으로 제어된다. AWS AppSync의 구독은 스키마에서 GraphQL 지시어로 제어되며, 스키마는 모든 데이터 소스를 사용할 수 있다. 따라서 Amazon DynamoDB와 Amazon Elasticsearch Service를 통해 데이터베이스 작업에서 알림을 트리거하거나, AWS Lambda를 통해 인프라의 다른 부분에서 알림을 트리거할 수 있다.

AWS Amplify와 유사하게, AWS AppSync를 사용해 GraphQL API에 엔터프라이즈 보안 기능을 적용할 수 있다. 이 서비스는 API 키를 통해 빠르게 시작할 수 있게 해준다. 하지만 프로덕션 환경으로 전환할 때는 AWS Identity and Access Management(IAM) 또는 Amazon Cognito 사용자 풀의 OIDC 토큰을 사용할 수 있다. 타입에 대한 정책을 통해 리졸버 수준에서 접근을 제어할 수 있다. 또한 런타임에 세분화된 접근 제어를 위해 사용자가 특정 데이터베이스 리소스의 소유자인지 확인하는 등의 논리적 검사를 수행할 수 있다. 리졸버 실행 또는 개별 데이터베이스 레코드 접근을 위해 그룹 멤버십을 확인하는 기능도 제공한다.

React Native 개발자가 이 기술을 더 쉽게 배울 수 있도록, AWS AppSync 콘솔 홈페이지에서 내장된 GraphQL 샘플 스키마를 실행할 수 있다. 이 샘플은 GraphQL 스키마를 배포하고, 데이터베이스 테이블을 프로비저닝하며, 쿼리, 뮤테이션, 구독을 자동으로 연결한다. 또한 이 내장 스키마를 활용한 React Native 예제React 예제도 제공된다. 이를 통해 클라이언트와 클라우드 컴포넌트를 몇 분 만에 실행할 수 있다.

AWSAppSyncClient를 사용하면 시작이 간단하다. 이 클라이언트는 Apollo Client에 연결된다. AWSAppSyncClient는 GraphQL API의 보안 및 서명, 오프라인 기능, 구독 핸드셰이크 및 협상 프로세스를 처리한다:

import AWSAppSyncClient from "aws-appsync";
import { Rehydrated } from 'aws-appsync-react';
import { AUTH_TYPE } from "aws-appsync/lib/link/auth-link";

const client = new AWSAppSyncClient({
url: awsconfig.graphqlEndpoint,
region: awsconfig.region,
auth: {type: AUTH_TYPE.API_KEY, apiKey: awsconfig.apiKey}
});

AppSync 콘솔은 GraphQL 엔드포인트, AWS 리전, API 키가 포함된 설정 파일을 제공한다. 그런 다음 React Apollo와 함께 클라이언트를 사용할 수 있다:

const WithProvider = () => (
<ApolloProvider client={client}>
<Rehydrated>
<App />
</Rehydrated>
</ApolloProvider>
);

이제 표준 GraphQL 쿼리를 사용할 수 있다:

query ListEvents {
listEvents{
items{
__typename
id
name
where
when
description
comments{
__typename
items{
__typename
eventId
commentId
content
createdAt
}
nextToken
}
}
}
}

위 예제는 AppSync가 프로비저닝한 샘플 앱 스키마를 사용한 쿼리를 보여준다. 이 예제는 DynamoDB와의 상호작용뿐만 아니라 데이터 페이징(암호화된 토큰 포함)과 EventsComments 간의 타입 관계도 보여준다. 앱이 AWSAppSyncClient로 구성되어 있기 때문에 데이터는 자동으로 오프라인에 저장되고, 디바이스가 다시 연결될 때 동기화된다.

이 비디오에서 이 기술의 심층 분석과 React Native 데모를 확인할 수 있다.

피드백

라이브러리를 개발한 팀은 여러분이 이 라이브러리와 서비스를 어떻게 활용하고 있는지 듣고 싶어 한다. 또한 React와 React Native를 클라우드 서비스와 함께 사용할 때 개발을 더 쉽게 만들기 위해 어떤 부분을 개선할 수 있는지 의견을 구하고 있다. AWS 모바일 팀에 연락하려면 AWS Amplify 또는 AWS AppSync GitHub 페이지를 방문하면 된다.

트위터 앱 로딩 애니메이션을 React Native로 구현하기

· 19 min read
Eli White
Eli White
Software Engineer at Meta

Twitter의 iOS 앱에 있는 로딩 애니메이션이 마음에 든다.

앱이 준비되면 Twitter 로고가 확장되며 앱이 나타난다.

이 로딩 애니메이션을 React Native로 재현하는 방법을 찾아보고 싶었다.


이 애니메이션을 어떻게 구현할지 이해하기 위해, 먼저 로딩 애니메이션의 세부 요소를 분석했다. 미묘한 차이를 확인하려면 애니메이션 속도를 늦춰보는 것이 가장 좋다.

이 애니메이션에는 몇 가지 주요 요소가 있다. 이 요소들을 어떻게 구현할지 알아내야 한다.

  1. 새 모양의 로고를 확대한다.
  2. 로고가 커지면서 앱이 밑에서 나타난다.
  3. 마지막에 앱을 약간 축소한다.

이 애니메이션을 만드는 데 꽤 오랜 시간이 걸렸다.

처음에는 파란색 배경과 Twitter 로고가 앱 위에 있는 레이어라고 잘못 생각했다. 로고가 커지면서 투명해지면 앱이 나타나는 것이라고 가정했는데, 이 방식은 작동하지 않았다. 로고가 투명해지면 파란색 레이어가 보이지 앱이 보이지 않기 때문이다.

다행히 여러분은 내가 겪었던 실수를 반복하지 않고도 이 튜토리얼을 통해 바로 핵심을 배울 수 있다!


올바른 접근 방식

코드를 살펴보기 전에, 이 효과를 어떻게 분해할지 이해하는 것이 중요하다. 이 효과를 시각적으로 이해하기 위해 CodePen에서 재현했다(몇 단락 아래에 임베드됨). 이를 통해 여러분은 각 레이어를 인터랙티브하게 확인할 수 있다.

이 효과에는 세 가지 주요 레이어가 있다. 첫 번째는 파란색 배경 레이어다. 이 레이어는 앱 위에 나타나는 것처럼 보이지만, 실제로는 가장 뒤에 위치한다.

그 다음은 단순한 흰색 레이어다. 마지막으로 가장 앞쪽에는 앱이 위치한다.


이 애니메이션의 핵심은 Twitter 로고를 mask로 사용해 앱과 흰색 레이어를 모두 마스킹하는 것이다. 마스킹의 세부 사항은 깊이 다루지 않겠다. 여기, 여기, 여기와 같은 온라인 리소스가 많이 있다.

이 컨텍스트에서 마스킹의 기본 개념은 마스크의 불투명한 픽셀이 마스킹하는 내용을 보여주고, 투명한 픽셀은 마스킹하는 내용을 숨기는 것이다.

Twitter 로고를 마스크로 사용해 두 레이어를 마스킹한다. 단색 흰색 레이어와 앱 레이어다.

앱을 드러내기 위해 마스크를 전체 화면보다 크게 확대한다.

마스크가 확대되는 동안 앱 레이어의 투명도를 서서히 높여 앱을 보여주고, 뒤에 있는 흰색 레이어를 숨긴다. 효과를 마무리하기 위해 앱 레이어를 1보다 큰 크기에서 시작해 애니메이션이 끝날 때 1로 축소한다. 그런 다음, 더 이상 보이지 않을 비앱 레이어를 숨긴다.

사진 한 장이 천 마디 말보다 낫다고 한다. 인터랙티브한 시각화는 몇 마디 말에 해당할까? "Next Step" 버튼을 클릭해 애니메이션을 단계별로 확인해 보자. 레이어를 보여주면 측면 뷰를 확인할 수 있다. 그리드는 투명한 레이어를 시각적으로 이해하는 데 도움을 준다.

React Native로 구현하기

이제 우리가 만들 애니메이션의 동작 방식을 이해했으니, 본격적으로 코드를 작성해 보자. 여러분이 이 글을 읽는 주된 이유일 것이다.

이 퍼즐의 핵심은 React Native의 코어 컴포넌트 중 하나인 MaskedViewIOS다.

import {MaskedViewIOS} from 'react-native';

<MaskedViewIOS maskElement={<Text>Basic Mask</Text>}>
<View style={{backgroundColor: 'blue'}} />
</MaskedViewIOS>;

MaskedViewIOSmaskElementchildren을 props로 받는다. childrenmaskElement에 의해 마스킹된다. 마스크는 이미지일 필요가 없으며, 어떤 뷰든 가능하다. 위 예제의 동작은 파란색 뷰를 렌더링하지만, maskElement의 "Basic Mask"라는 텍스트가 있는 부분에서만 보이게 된다. 우리는 단순히 복잡한 파란색 텍스트를 만든 것이다.

우리가 원하는 것은 파란색 레이어를 렌더링하고, 그 위에 Twitter 로고가 있는 마스크와 흰색 레이어를 렌더링하는 것이다.

{
fullScreenBlueLayer;
}
<MaskedViewIOS
style={{flex: 1}}
maskElement={
<View style={styles.centeredFullScreen}>
<Image source={twitterLogo} />
</View>
}>
{fullScreenWhiteLayer}
<View style={{flex: 1}}>
<MyApp />
</View>
</MaskedViewIOS>;

이 코드는 아래와 같은 레이어를 만들어 준다.

애니메이션 구현하기

이제 모든 준비가 끝났으니, 다음 단계는 애니메이션을 적용하는 것이다. 자연스러운 애니메이션을 만들기 위해 React Native의 Animated API를 활용한다.

Animated를 사용하면 자바스크립트에서 선언적으로 애니메이션을 정의할 수 있다. 기본적으로 이 애니메이션은 자바스크립트에서 실행되며, 매 프레임마다 네이티브 레이어에 어떤 변경을 해야 하는지 알려준다. 하지만 자바스크립트가 매 프레임마다 애니메이션을 업데이트하려고 해도 충분히 빠르게 처리하지 못해 프레임이 누락되는 현상(지연)이 발생할 수 있다. 이는 우리가 원하는 결과가 아니다!

Animated는 이러한 지연 없이 애니메이션을 구현할 수 있도록 특별한 기능을 제공한다. Animated에는 useNativeDriver라는 플래그가 있는데, 이는 애니메이션 시작 시 자바스크립트에서 네이티브로 애니메이션 정의를 전달한다. 이를 통해 네이티브 측에서 매 프레임마다 자바스크립트와 통신하지 않고도 애니메이션 업데이트를 처리할 수 있다. useNativeDriver의 단점은 특정 속성만 업데이트할 수 있다는 점이다. 주로 transformopacity 속성을 사용할 수 있다. useNativeDriver로는 배경색과 같은 속성을 애니메이션할 수 없다. 아직은 그렇지만, 시간이 지나면서 더 많은 속성을 추가할 예정이다. 물론 필요한 속성을 직접 PR로 제출해 커뮤니티 전체에 기여할 수도 있다 😀.

이 애니메이션을 부드럽게 만들기 위해 이러한 제약 조건 내에서 작업할 것이다. useNativeDriver의 내부 동작을 더 깊이 이해하려면 공식 블로그 포스트를 참고한다.

애니메이션 단계별 분석

애니메이션은 크게 4가지 단계로 구성된다:

  1. 새를 확대하며 앱과 흰색 레이어를 드러낸다.
  2. 앱을 서서히 나타나게 한다.
  3. 앱의 크기를 축소한다.
  4. 흰색 레이어와 파란색 레이어를 숨긴다.

Animated를 사용해 애니메이션을 정의하는 두 가지 주요 방법이 있다. 첫 번째는 Animated.timing을 사용하는 방법으로, 애니메이션의 지속 시간과 움직임을 부드럽게 하는 이징 곡선을 직접 지정할 수 있다. 두 번째는 물리 기반 API인 Animated.spring를 사용하는 방법이다. Animated.spring에서는 스프링의 마찰력과 장력을 지정하고 물리 엔진이 애니메이션을 실행하도록 한다.

여러 애니메이션을 동시에 실행해야 하며, 이들은 서로 밀접하게 연관되어 있다. 예를 들어, 마스크가 드러나는 중간에 앱을 서서히 나타나게 해야 한다. 이러한 애니메이션은 서로 밀접하게 연결되어 있기 때문에, 단일 Animated.Value를 사용해 Animated.timing으로 구현할 것이다.

Animated.Value는 애니메이션의 상태를 나타내는 네이티브 값을 감싼 래퍼다. 일반적으로 전체 애니메이션에 대해 하나의 Animated.Value만 사용한다. Animated를 사용하는 대부분의 컴포넌트는 이 값을 상태에 저장한다.

이 애니메이션을 시간에 따라 진행되는 단계로 생각하기 때문에, Animated.Value를 0(0% 완료)에서 시작해 100(100% 완료)로 끝나도록 설정할 것이다.

초기 컴포넌트 상태는 다음과 같다.

state = {
loadingProgress: new Animated.Value(0),
};

애니메이션을 시작할 준비가 되면, Animated에게 이 값을 100으로 애니메이션하도록 지시한다.

Animated.timing(this.state.loadingProgress, {
toValue: 100,
duration: 1000,
useNativeDriver: true, // 이 부분이 중요하다!
}).start();

그런 다음 애니메이션의 각 부분과 전체 애니메이션 진행에 따라 원하는 값을 추정한다. 아래 표는 애니메이션의 각 부분과 시간에 따라 달라지는 값을 보여준다.

트위터 새 마스크는 스케일 1에서 시작해 작아졌다가 다시 크게 확대된다. 애니메이션의 10% 지점에서 스케일 값은 0.8이 되고, 마지막에는 스케일 70까지 확대된다. 70이라는 값은 사실 꽤 임의적으로 정한 것이다. 새가 화면을 완전히 드러낼 수 있을 만큼 충분히 커야 했고, 60으로는 부족했다.😀 흥미로운 점은 이 숫자가 클수록 같은 시간 안에 더 빠르게 커지는 것처럼 보인다는 것이다. 이 로고에 적합한 값을 찾기 위해 여러 번 시도했다. 로고나 디바이스 크기가 다르면 화면을 완전히 드러내기 위해 이 최종 스케일 값을 조정해야 한다.

앱은 트위터 로고가 작아지는 동안 불투명하게 유지되어야 한다. 공식 애니메이션을 참고해, 새가 확대되는 중간에 앱을 보이기 시작하고 빠르게 완전히 드러나도록 했다. 따라서 애니메이션의 15% 지점에서 앱이 보이기 시작하고, 30% 지점에서 완전히 나타난다.

앱의 스케일은 1.1에서 시작해 애니메이션 끝에 정상 크기로 축소된다.

코드로 구현하기

위에서 한 작업은 애니메이션 진행률을 각 요소에 적용할 값으로 매핑하는 것이다. Animated.interpolate를 사용해 이를 구현한다. this.state.loadingProgress를 기반으로 보간된 값을 사용해 애니메이션의 각 부분에 대한 스타일 객체를 3개 생성한다.

const loadingProgress = this.state.loadingProgress;

const opacityClearToVisible = {
opacity: loadingProgress.interpolate({
inputRange: [0, 15, 30],
outputRange: [0, 0, 1],
extrapolate: 'clamp',
// clamp는 입력이 30-100일 때 출력을 1로 유지한다는 의미
}),
};

const imageScale = {
transform: [
{
scale: loadingProgress.interpolate({
inputRange: [0, 10, 100],
outputRange: [1, 0.8, 70],
}),
},
],
};

const appScale = {
transform: [
{
scale: loadingProgress.interpolate({
inputRange: [0, 100],
outputRange: [1.1, 1],
}),
},
],
};

이제 이 스타일 객체를 사용해 앞서 설명한 뷰 스니펫을 렌더링할 수 있다. Animated.Value를 사용하는 스타일 객체는 Animated.View, Animated.Text, Animated.Image만 사용할 수 있다는 점에 유의한다.

const fullScreenBlueLayer = (
<View style={styles.fullScreenBlueLayer} />
);
const fullScreenWhiteLayer = (
<View style={styles.fullScreenWhiteLayer} />
);

return (
<View style={styles.fullScreen}>
{fullScreenBlueLayer}
<MaskedViewIOS
style={{flex: 1}}
maskElement={
<View style={styles.centeredFullScreen}>
<Animated.Image
style={[styles.maskImageStyle, imageScale]}
source={twitterLogo}
/>
</View>
}>
{fullScreenWhiteLayer}
<Animated.View
style={[opacityClearToVisible, appScale, {flex: 1}]}>
{this.props.children}
</Animated.View>
</MaskedViewIOS>
</View>
);

이제 애니메이션 조각이 원하는 대로 보인다. 이제 더 이상 보이지 않을 파란색과 흰색 레이어를 정리하기만 하면 된다.

이 레이어를 언제 정리할지 알려면 애니메이션이 완료된 시점을 알아야 한다. 다행히 Animated.timing을 호출할 때 .start는 애니메이션이 완료되면 실행되는 콜백을 선택적으로 받는다.

Animated.timing(this.state.loadingProgress, {
toValue: 100,
duration: 1000,
useNativeDriver: true,
}).start(() => {
this.setState({
animationDone: true,
});
});

이제 애니메이션이 완료되었는지 알 수 있는 state 값을 가지게 되었으므로, 파란색과 흰색 레이어를 수정해 이 값을 사용할 수 있다.

const fullScreenBlueLayer = this.state.animationDone ? null : (
<View style={[styles.fullScreenBlueLayer]} />
);
const fullScreenWhiteLayer = this.state.animationDone ? null : (
<View style={[styles.fullScreenWhiteLayer]} />
);

이제 애니메이션이 작동하고, 애니메이션이 완료되면 사용하지 않는 레이어를 정리한다. 트위터 앱 로딩 애니메이션을 완성했다!

잠깐, 내 코드가 작동하지 않아요!

걱정하지 마세요. 저도 가이드에서 코드 조각만 제공하고 완성된 소스를 보여주지 않을 때 답답함을 느낍니다.

이 컴포넌트는 npm에 등록되어 있으며, GitHub에서 react-native-mask-loader로 확인할 수 있습니다. 여러분의 휴대폰에서 직접 테스트해보고 싶다면, Expo에서도 사용 가능합니다.

추가 학습 자료 / 심화 과제

  1. 이 gitbook은 React Native 문서를 읽은 후 Animated에 대해 더 깊이 배우기에 좋은 자료다.
  2. 실제 트위터 애니메이션은 끝 부분에서 마스크가 더 빨리 드러나는 것처럼 보인다. 로더를 수정해 다른 easing 함수(또는 spring!)를 사용해 그 동작을 더 잘 맞춰보자.
  3. 현재 마스크의 end-scale은 하드 코딩되어 있으며, 태블릿에서는 전체 앱이 드러나지 않을 가능성이 있다. 화면 크기와 이미지 크기를 기반으로 end scale을 계산해보는 것도 멋진 PR이 될 것이다.

React Native Monthly #6

· 7 min read
Tomislav Tenodi
Speck 창립자

리액트 네이티브 월간 미팅은 여전히 활발히 진행 중이다! 다음 세션에 대한 안내는 이 글의 하단에 있는 노트를 꼭 확인하길 바란다.

Expo

  • Devin AbbottHoussein Djirdeh가 "Full Stack React Native" 책의 프리릴리즈를 발표한 것을 축하한다! 이 책은 여러 작은 앱을 만들어가며 React Native를 배우는 과정을 안내한다. 책을 구매하기 전에 https://www.fullstackreact.com/react-native/에서 해당 앱들을 직접 시험해볼 수 있다.

  • ReasonML을 쉽게 시도해볼 수 있도록 도와주는 reason-react-native-scripts의 첫 번째(실험적) 버전을 출시했다.

  • Expo SDK 24가 출시되었다! 이번 버전은 React Native 0.51을 기반으로 하며, 여러 새로운 기능과 개선사항을 포함한다: 스탠드얼론 앱에서 이미지 번들링(첫 로드 시 캐시할 필요 없음!), 이미지 조작 API(자르기, 크기 조정, 회전, 뒤집기), 얼굴 인식 API, 새로운 릴리스 채널 기능(특정 채널에 대한 활성 릴리스 설정 및 롤백), 스탠드얼론 앱 빌드를 추적할 수 있는 웹 대시보드, OpenGL Android 구현과 Android 멀티태스커의 오랜 버그 수정 등이 있다.

  • 이번 1월부터 React Navigation에 더 많은 리소스를 할당하기로 했다. React 컴포넌트와 Animated, react-native-gesture-handler 같은 기본 요소만으로 React Native 네비게이션을 구축하는 것이 가능하고 바람직하다고 강력히 믿으며, 계획된 몇 가지 개선사항에 대해 매우 기대하고 있다. 커뮤니티에 기여하고 싶다면 react-native-mapsreact-native-svg를 확인해보길 바란다. 두 프로젝트 모두 도움이 필요하다!

Infinite Red

Microsoft

  • 풀 리퀘스트가 시작되어 React Native Windows 브릿지를 .NET Standard로 마이그레이션하는 작업이 진행 중이다. 이로 인해 OS에 구애받지 않는 구조로 변환될 전망이다. 이 작업이 완료되면 다양한 .NET Core 플랫폼에서 자체 스레딩 모델, JavaScript 런타임, 그리고 UIManager를 활용해 브릿지를 확장할 수 있게 된다. 예를 들어 JavaScriptCore, Xamarin.Mac, Linux Gtk#, 그리고 Samsung Tizen 같은 옵션들이 가능해진다.

Wix

  • Detox

    • E2E 테스트를 확장하기 위해 CI 시간을 최소화하려고 노력 중이다. 이를 위해 Detox의 병렬화 지원을 개발 중이다.
    • E2E에서 모킹을 더 잘 지원하기 위해 커스텀 플레이버 빌드를 지원하는 풀 리퀘스트를 제출했다.
  • DetoxInstruments

    • DetoxInstruments의 핵심 기능을 개발하는 것은 매우 도전적인 작업이다. 특정 시점에 JavaScript 백트레이스를 얻기 위해서는 JS 스레드 일시 중단을 지원하는 커스텀 JSCore 구현이 필요하다. Wix 앱에서 프로파일러를 테스트한 결과 JS 스레드에 대한 흥미로운 통찰을 얻었다.
    • 이 프로젝트는 아직 일반 사용에 충분히 안정적이지 않지만, 활발히 개발 중이며 곧 공개할 예정이다.
  • React Native Navigation

    • V2 개발 속도가 크게 증가했다. 지금까지는 1명의 개발자가 20%의 시간을 할애했지만, 이제는 3명의 개발자가 풀타임으로 작업 중이다.
  • Android 성능

    • RN에 번들된 구버전 JSCore를 최신 버전(webkitGTK 프로젝트의 최신 버전, 커스텀 JIT 설정 포함)으로 교체했더니 JS 스레드 성능이 40% 향상되었다. 다음 단계는 64비트 버전을 컴파일하는 것이다. 이 작업은 JSC 빌드 스크립트 for Android를 기반으로 한다. 현재 진행 상황은 여기에서 확인할 수 있다.

다음 회의 주제

이번 회의를 특정 주제에 초점을 맞춰 진행하는 방안에 대해 논의 중이다. 예를 들어 네비게이션, React Native 모듈을 별도 저장소로 분리, 문서화 등 구체적인 주제를 깊이 있게 다룰 예정이다. 이 방식을 통해 React Native 커뮤니티에 더 큰 기여를 할 수 있을 것으로 기대한다. 다음 회의에서 이 주제를 다룰 가능성이 높다. 다루고 싶은 주제가 있다면 트위터를 통해 의견을 공유해 주시기 바란다.

React Native Monthly #5

· 7 min read
Tomislav Tenodi
Founder at Speck

리액트 네이티브 월간 미팅이 계속됩니다! 각 팀이 어떤 작업을 진행 중인지 살펴봅시다.

Callstack

  • React Native CI 작업을 진행했다. 가장 중요한 것은 Travis에서 Circle로 마이그레이션하여 React Native가 단일 통합 CI 파이프라인을 갖추게 된 것이다.
  • Hacktoberfest - React Native 버전을 주최했다. 참가자들과 함께 오픈소스 프로젝트에 많은 풀 리퀘스트를 제출하려고 노력했다.
  • Haul 작업을 계속하고 있다. 지난달에는 webpack 3 지원을 포함한 두 가지 새로운 릴리스를 제출했다. CRNAExpo 지원을 추가하고 더 나은 HMR을 작업할 계획이다. 로드맵은 이슈 트래커에 공개되어 있다. 개선 사항을 제안하거나 피드백을 주고 싶다면 알려주기 바란다!

Expo

  • Expo SDK 22를 출시했다. (React Native 0.49 사용) 그리고 이를 위해 CRNA를 업데이트했다.
    • 개선된 스플래시 스크린 API, 기본 ARKit 지원, "DeviceMotion" API, iOS11에서의 SFAuthenticationSession 지원 등을 포함한다. 더 많은 내용은 블로그에서 확인할 수 있다.
  • Snack에서 이제 여러 JavaScript 파일을 사용할 수 있으며, 이미지와 다른 리소스를 에디터에 드래그 앤 드롭으로 업로드할 수 있다.
  • react-navigation에 기여하여 iPhone X 지원을 추가했다.
  • Expo로 대규모 애플리케이션을 구축할 때 발생하는 문제점에 집중했다. 예를 들어:
    • 스테이징, 프로덕션, 임의의 채널 등 여러 환경에 배포하는 퍼스트클래스 지원. 채널은 롤백 기능과 특정 채널에 대한 활성 릴리즈 설정을 지원한다. 초기 테스터가 되고 싶다면 @expo_io로 알려달라.
    • 독립 실행형 앱 빌드 인프라를 개선하고, 독립 실행형 앱 빌드에서 이미지와 코드가 아닌 다른 리소스를 번들링하는 동시에 리소스를 무선으로 업데이트할 수 있는 기능을 추가하는 작업도 진행 중이다.

Facebook

  • 더 나은 RTL 지원:
    • 방향을 인식하는 스타일을 여러 가지 도입한다.
      • 위치:
        • (left|right) → (start|end)
      • 마진:
        • margin(Left|Right) → margin(Start|End)
      • 패딩:
        • padding(Left|Right) → padding(Start|End)
      • 테두리:
        • borderTop(Left|Right)Radius → borderTop(Start|End)Radius
        • borderBottom(Left|Right)Radius → borderBottom(Start|End)Radius
        • border(Left|Right)Width → border(Start|End)Width
        • border(Left|Right)Color → border(Start|End)Color
    • RTL에서 위치, 마진, 패딩, 테두리 스타일의 "left"와 "right" 의미가 서로 바뀌었다. 몇 달 내에 이 동작을 제거하고 "left"는 항상 "왼쪽", "right"는 항상 "오른쪽"을 의미하도록 변경할 예정이다. 이 변경 사항은 플래그 아래에 숨겨져 있다. React Native 컴포넌트에서 I18nManager.swapLeftAndRightInRTL(false)를 사용해 이 변경을 적용할 수 있다.
  • Flow를 사용해 내부 네이티브 모듈의 타입을 정의하고, 이를 기반으로 Java에서는 인터페이스, ObjC에서는 프로토콜을 생성하는 작업을 진행 중이다. 이 코드 생성 도구가 내년 초에 오픈소스로 공개될 것으로 기대한다.

Infinite Red

  • React Native 및 기타 프로젝트를 지원하는 새로운 오픈소스 도구를 개발 중이다. 자세한 내용은 여기에서 확인할 수 있다.
  • 새로운 보일러플레이트 릴리스를 위해 Ignite를 개편 중이다 (코드명: Bowser).

Shoutem

  • Shoutem의 개발 프로세스를 개선했다. 앱 생성부터 첫 커스텀 화면 구현까지의 과정을 간소화하고, 새로운 React Native 개발자가 쉽게 접근할 수 있도록 했다. 새로운 기능을 시험하기 위해 여러 워크숍을 준비했다. 또한 Shoutem CLI를 개선해 새로운 워크플로를 지원한다.
  • Shoutem UI는 여러 컴포넌트 개선과 버그 수정을 거쳤다. 최신 React Native 버전과의 호환성도 확인했다.
  • Shoutem 플랫폼에는 몇 가지 주목할 만한 업데이트가 적용됐다. 새로운 통합 기능이 오픈소스 확장 프로젝트의 일부로 추가됐다. 다른 개발자들이 Shoutem 확장 기능을 적극적으로 개발하는 모습을 보며 기쁘게 생각한다. 우리는 그들의 확장 기능에 대해 조언과 가이드를 제공하며 적극적으로 소통한다.

다음 세션

다음 세션은 2017년 12월 6일 수요일에 예정되어 있다. 세션 결과물을 개선할 방법에 대한 제안이 있다면 트위터를 통해 언제든지 연락해 주세요.

React Native Monthly #4

· 6 min read
Mike Grabowski
Mike Grabowski
CTO and Co-Founder at Callstack

React Native 월간 회의가 계속되고 있다! 각 팀의 진행 상황 요약은 다음과 같다:

콜스택 소식

  • React Native EU가 종료되었다. 33개국에서 온 300명 이상의 참가자가 브로츠와프를 방문했다. 발표 내용은 YouTube에서 확인할 수 있다.
  • 컨퍼런스 이후 오픈 소스 일정으로 천천히 복귀하고 있다. 현재 react-native-opentok의 다음 버전을 작업 중이며, 대부분의 기존 문제를 해결할 예정이다.

GeekyAnts

개발자들이 React Native를 더 쉽게 접할 수 있도록 다음과 같은 노력을 기울이고 있다:

  • React Native EU에서 BuilderX.io를 발표했다. BuilderX는 JavaScript 파일(현재는 React Native만 지원)과 직접 연동되어 아름답고 가독성 높으며 편집 가능한 코드를 생성하는 디자인 도구다.
  • ReactNativeSeed.com을 출시했다. 이 사이트는 다음 React Native 프로젝트를 위한 다양한 보일러플레이트를 제공한다. 데이터 타입을 위한 TypeScript와 Flow, 상태 관리를 위한 MobX, Redux, mobx-state-tree 등 다양한 옵션과 함께 CRNA 및 일반 React-Native 스택을 지원한다.

Expo

  • 곧 SDK 21을 출시할 예정이다. 이번 업데이트는 react-native 0.48.3을 지원하며, 비디오 녹화, 새로운 스플래시 화면 API, react-native-gesture-handler 지원, 그리고 개선된 오류 처리 기능을 포함한 다양한 버그 수정 및 신뢰성 향상이 이루어졌다.

  • react-native-gesture-handler에 대해, Software MansionKrzysztof Magiera가 이 프로젝트를 계속해서 발전시키고 있다. Expo 팀은 이 프로젝트의 테스트를 지원하고 개발 시간의 일부를 후원하고 있다. SDK21에서 이 기능이 통합되면 Snack에서 쉽게 사용해볼 수 있게 되어, 커뮤니티에서 어떤 결과물을 만들어낼지 기대된다.

  • 개선된 오류 로깅 및 처리에 대해서는 이 Expo 내부 PR의 gist에서 자세한 로깅 정보를 확인할 수 있다. 특히 "Problem 2"를 참고하면 된다. 또한 이 커밋은 npm 표준 라이브러리 모듈을 가져오려고 시도했을 때 실패한 경우를 처리하는 변경 사항을 보여준다. React Native에서 오류 메시지를 개선할 여지가 많으며, Expo 팀은 이에 대한 후속 PR을 작업할 예정이다. 커뮤니티의 참여도 매우 환영한다.

  • native.directory는 계속해서 성장하고 있다. 여러분의 프로젝트를 GitHub 저장소에서 추가할 수 있다.

  • 북미 전역의 해커톤에 참여할 예정이다. PennApps, Hack The North, HackMIT, 그리고 곧 MHacks 등을 방문할 계획이다.

Facebook

  • Android에서 <Text><TextInput> 컴포넌트 개선 작업을 진행 중이다. (<TextInput>의 자동 크기 조절 기능, 깊게 중첩된 <Text> 컴포넌트의 레이아웃 문제 해결, 더 나은 코드 구조, 성능 최적화 등)
  • 이슈 관리와 풀 리퀘스트 검토를 도와줄 추가 기여자를 여전히 찾고 있다.

마이크로소프트

  • CodePush에 코드 서명 기능을 추가했다. 이제 React Native 개발자들이 CodePush에서 애플리케이션 번들에 서명할 수 있다. 자세한 내용은 여기에서 확인할 수 있다.
  • CodePush를 Mobile Center와 통합하는 작업을 진행 중이다. 테스트 및 크래시 통합도 고려하고 있다.

다음 세션

다음 세션은 2017년 10월 10일 수요일에 예정되어 있다. 이번이 네 번째 모임이기 때문에, 우리는 이 자료가 React Native 커뮤니티에 어떤 도움을 주는지 알고 싶다. 만약 모임의 결과물을 어떻게 개선할지 제안할 사항이 있다면, 트위터를 통해 언제든지 연락해 주기 바란다.

React Native Monthly #3

· 8 min read
Mike Grabowski
Mike Grabowski
CTO and Co-Founder at Callstack

React Native 월간 미팅이 계속됩니다! 이번 달 미팅은 대부분의 팀이 출시 작업으로 바빠서 조금 짧게 진행됐습니다. 다음 달에는 폴란드 브로츠와프에서 열리는 React Native EU 컨퍼런스에 참석합니다. 티켓을 구매하고 현장에서 만나요! 그동안 우리 팀이 어떤 일을 하고 있는지 살펴보겠습니다.

팀 소개

세 번째 모임에는 다음 5개 팀이 참여했다:

노트

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

Callstack

  • 최근 오픈소스로 공개한 react-native-material-palette는 이미지에서 주요 색상을 추출해 시각적으로 매력적인 앱을 만들 수 있도록 도와준다. 현재는 Android만 지원하지만, 향후 iOS 지원도 추가할 계획이다.
  • haul에 HMR(Hot Module Replacement) 지원을 추가했고, 여러 멋진 기능도 함께 포함했다. 최신 릴리즈를 확인해 보자.
  • React Native EU 2017이 다가온다! 다음 달은 폴란드에서 React Native에 관한 모든 것을 다룰 예정이다. 남은 티켓을 여기에서 서둘러 구매하자.

Expo

  • Snack에서 npm 패키지 설치를 지원한다. 기존 Expo 제한 사항이 적용되며, 패키지는 Expo에 포함되지 않은 커스텀 네이티브 API에 의존할 수 없다. 또한 Snack에서 여러 파일 지원과 에셋 업로드 기능을 추가하기 위해 작업 중이다. SatyajitReact Native Europe에서 Snack에 대해 발표할 예정이다.
  • SDK20을 출시했다. 카메라, 결제, 보안 스토리지, 자기계, 파일 다운로드 일시 중지/재개 기능, 그리고 스플래시/로딩 화면 개선이 포함된다.
  • Krzysztof와 함께 react-native-gesture-handler 작업을 계속 진행 중이다. PanResponder나 네이티브 제스처 인식기를 사용해 이전에 구현한 제스처를 다시 만들어 보고, 문제가 발생하면 알려달라고 요청한다.
  • JSC 디버깅 프로토콜을 실험 중이며, Canny에서 다양한 기능 요청을 처리하고 있다.

Facebook

  • 지난달에는 GitHub 이슈 트래커 관리에 대해 논의했고, 프로젝트의 유지 보수성을 개선하기 위해 노력하겠다고 이야기했다.
  • 현재 오픈된 이슈 수는 약 600개 수준으로 유지되고 있으며, 당분간 이 상태가 계속될 것으로 보인다. 지난달 동안 활동이 없는 이슈(최근 60일 동안 댓글이 없는 경우) 690개를 닫았다. 이 중 58개 이슈는 다양한 이유로 다시 열렸다(메인테이너가 수정을 약속하거나, 기여자가 이슈를 계속 열어둘 만한 타당한 이유를 제시한 경우).
  • 당분간 활동이 없는 이슈를 자동으로 닫는 정책을 계속 유지할 계획이다. 이슈 트래커에 등록된 모든 중요한 이슈가 적절히 처리되는 상태를 목표로 하고 있지만, 아직 그 수준에는 이르지 못했다. 메인테이너들의 도움이 절실하다. 특히 새로 생성된 프로젝트에 영향을 미치는 회귀 버그나 주요 변경 사항을 놓치지 않도록 이슈를 분류하고 관리해야 한다. 도움을 주고 싶은 사람은 Facebook GitHub Bot을 사용해 이슈와 풀 리퀘스트를 분류할 수 있다. 새로운 메인테이너 가이드에는 이슈 분류와 GitHub Bot 사용 방법에 대한 자세한 정보가 포함되어 있다. 이슈 태스크 포스에 참여하고, 다른 활발한 커뮤니티 멤버들도 참여하도록 독려해 주길 바란다!

마이크로소프트

  • 새로운 Skype 앱은 플랫폼 간 코드 공유를 최대화하기 위해 React Native를 기반으로 개발되었다. React Native 기반의 Skype 앱은 현재 Android와 iOS 앱 스토어에서 이용 가능하다.
  • Skype 앱을 React Native로 개발하면서, 발견한 버그와 누락된 기능을 해결하기 위해 React Native에 풀 리퀘스트를 보냈다. 지금까지 약 70개의 풀 리퀘스트가 병합되었다.
  • React Native를 통해 Android와 iOS Skype 앱을 동일한 코드베이스로 구동할 수 있었다. 이 코드베이스를 Skype 웹 앱에도 활용하고자 한다. 이를 위해 React/React Native 위에 얇은 레이어인 ReactXP를 개발하고 오픈소스로 공개했다. ReactXP는 iOS/Android를 타겟팅할 때는 React Native로, 웹을 타겟팅할 때는 react-dom으로 매핑되는 크로스 플랫폼 컴포넌트를 제공한다. ReactXP의 목표는 React Native for Web이라는 오픈소스 라이브러리와 유사하다. 두 라이브러리의 접근 방식 차이에 대한 간단한 설명은 ReactXP FAQ에서 확인할 수 있다.

Shoutem

  • Shoutem을 사용해 앱을 개발할 때 개발자 경험을 개선하고 단순화하기 위한 노력을 계속하고 있다.
  • 모든 앱을 react-navigation으로 마이그레이션하기 시작했지만, 더 안정적인 버전이 출시되거나 네이티브 네비게이션 솔루션 중 하나가 안정화될 때까지 이 작업을 연기하기로 결정했다.
  • 모든 extensions과 대부분의 오픈소스 라이브러리(animation, theme, ui)를 React Native 0.47.1로 업데이트하고 있다.

다음 세션

다음 세션은 2017년 9월 13일 수요일에 예정되어 있다. 이번이 세 번째 모임이므로, 이 노트가 React Native 커뮤니티에 어떻게 도움이 되는지 알고 싶다. 모임 결과물을 개선할 방법에 대한 제안이 있다면 트위터를 통해 언제든지 연락해 주세요.