네이티브와 React Native 간의 통신
기존 앱과 통합 가이드와 네이티브 UI 컴포넌트 가이드에서 우리는 네이티브 컴포넌트에 React Native를 포함하거나 그 반대의 경우를 배웠다. 네이티브와 React Native 컴포넌트를 혼합할 때, 이 두 세계 간의 통신이 필요할 때가 있다. 다른 가이드에서 이미 몇 가지 방법을 소개했다. 이 글은 사용 가능한 기술들을 요약한다.
소개
React Native는 React에서 영감을 받았기 때문에 정보 흐름의 기본 개념이 비슷하다. React의 흐름은 단방향이다. 컴포넌트의 계층 구조를 유지하며, 각 컴포넌트는 부모 컴포넌트와 자신의 내부 상태에만 의존한다. 이를 위해 프로퍼티를 사용한다. 데이터는 부모에서 자식으로 하향식으로 전달된다. 만약 조상 컴포넌트가 자손 컴포넌트의 상태에 의존한다면, 자손 컴포넌트가 조상 컴포넌트를 업데이트할 수 있도록 콜백을 전달해야 한다.
이 같은 개념이 React Native에도 적용된다. 프레임워크 내에서만 애플리케이션을 구축한다면, 프로퍼티와 콜백을 통해 앱을 구동할 수 있다. 하지만 React Native와 네이티브 컴포넌트를 혼합할 때는, 이들 사이에서 정보를 전달할 수 있는 특수한 크로스 언어 메커니즘이 필요하다.
속성(Properties)
속성은 컴포넌트 간 통신을 위한 가장 직관적인 방법이다. 따라서 네이티브에서 리액트 네이티브로, 그리고 리액트 네이티브에서 네이티브로 속성을 전달할 수 있는 방법이 필요하다.
네이티브에서 React Native로 속성 전달하기
메인 액티비티에서 ReactActivityDelegate
의 커스텀 구현을 제공하여 React Native 앱으로 속성을 전달할 수 있다. 이 구현은 getLaunchOptions
를 오버라이드하여 원하는 속성이 포함된 Bundle
을 반환해야 한다.
- Java
- Kotlin
public class MainActivity extends ReactActivity {
@Override
protected ReactActivityDelegate createReactActivityDelegate() {
return new ReactActivityDelegate(this, getMainComponentName()) {
@Override
protected Bundle getLaunchOptions() {
Bundle initialProperties = new Bundle();
ArrayList<String> imageList = new ArrayList<String>(Arrays.asList(
"https://dummyimage.com/600x400/ffffff/000000.png",
"https://dummyimage.com/600x400/000000/ffffff.png"
));
initialProperties.putStringArrayList("images", imageList);
return initialProperties;
}
};
}
}
class MainActivity : ReactActivity() {
override fun createReactActivityDelegate(): ReactActivityDelegate {
return object : ReactActivityDelegate(this, mainComponentName) {
override fun getLaunchOptions(): Bundle {
val imageList = arrayListOf("https://dummyimage.com/600x400/ffffff/000000.png", "https://dummyimage.com/600x400/000000/ffffff.png")
val initialProperties = Bundle().apply { putStringArrayList("images", imageList) }
return initialProperties
}
}
}
}
import React from 'react';
import {View, Image} from 'react-native';
export default class ImageBrowserApp extends React.Component {
renderImage(imgURI) {
return <Image source={{uri: imgURI}} />;
}
render() {
return <View>{this.props.images.map(this.renderImage)}</View>;
}
}
ReactRootView
는 읽기-쓰기 속성인 appProperties
를 제공한다. appProperties
가 설정되면, React Native 앱은 새로운 속성으로 리렌더링된다. 업데이트는 이전 속성과 새로운 속성이 다를 때만 수행된다.
- Java
- Kotlin
Bundle updatedProps = mReactRootView.getAppProperties();
ArrayList<String> imageList = new ArrayList<String>(Arrays.asList(
"https://dummyimage.com/600x400/ff0000/000000.png",
"https://dummyimage.com/600x400/ffffff/ff0000.png"
));
updatedProps.putStringArrayList("images", imageList);
mReactRootView.setAppProperties(updatedProps);
var updatedProps: Bundle = reactRootView.getAppProperties()
var imageList = arrayListOf("https://dummyimage.com/600x400/ff0000/000000.png", "https://dummyimage.com/600x400/ffffff/ff0000.png")
속성은 언제든지 업데이트할 수 있다. 하지만 업데이트는 메인 스레드에서 수행해야 한다. getter는 어떤 스레드에서든 사용할 수 있다.
한 번에 일부 속성만 업데이트할 방법은 없다. 대신 자신의 래퍼에 이를 구축할 것을 권장한다.
참고: 현재, 최상위 RN 컴포넌트의 JS 함수
componentWillUpdateProps
는 속성 업데이트 후 호출되지 않는다. 하지만componentDidMount
함수에서 새로운 속성에 접근할 수 있다.
React Native에서 네이티브로 속성 전달하기
네이티브 컴포넌트의 속성을 노출하는 문제는 이 글에서 자세히 다룬다. 간단히 말해, JavaScript에서 반영할 속성은 @ReactProp
어노테이션으로 설정된 setter 메서드로 노출해야 한다. 그런 다음 이 컴포넌트를 일반 React Native 컴포넌트처럼 사용할 수 있다.
프로퍼티의 한계
크로스 언어 프로퍼티의 주요 단점은 콜백을 지원하지 않는다는 점이다. 콜백이 있다면 상향식 데이터 바인딩을 처리할 수 있다. 예를 들어, JS 액션의 결과로 네이티브 부모 뷰에서 작은 RN 뷰를 제거하려는 상황을 상상해보자. 프로퍼티로는 이를 구현할 방법이 없다. 정보가 상향식으로 전달되어야 하기 때문이다.
크로스 언어 콜백의 일부 기능(여기에서 설명)이 있지만, 이 콜백은 항상 필요한 기능을 제공하지는 않는다. 주요 문제는 이 콜백이 프로퍼티로 전달되도록 설계되지 않았다는 점이다. 대신 이 메커니즘은 JS에서 네이티브 액션을 트리거하고, 그 액션의 결과를 JS에서 처리할 수 있게 해준다.
다른 언어 간 상호작용 방법 (이벤트와 네이티브 모듈)
앞서 언급했듯이, 프로퍼티를 사용하는 데는 몇 가지 제약이 있다. 때로는 프로퍼티만으로는 앱의 로직을 제어하기에 부족하며, 더 유연한 해결책이 필요하다. 이 장에서는 React Native에서 사용할 수 있는 다른 통신 기술을 다룬다. 이 기술들은 내부 통신(React Native의 JS와 네이티브 레이어 간)뿐만 아니라 외부 통신(React Native와 앱의 '순수 네이티브' 부분 간)에도 활용할 수 있다.
React Native는 크로스-언어 함수 호출을 가능하게 한다. JS에서 커스텀 네이티브 코드를 실행하거나 그 반대로 실행할 수 있다. 하지만 작업하는 측면에 따라 동일한 목표를 달성하는 방법이 다르다. 네이티브 측에서는 JS에서 핸들러 함수를 실행하기 위해 이벤트 메커니즘을 사용하는 반면, React Native 측에서는 네이티브 모듈에서 내보낸 메서드를 직접 호출한다.
React Native 함수를 네이티브에서 호출하기 (이벤트)
이벤트에 대한 자세한 내용은 이 문서에서 확인할 수 있다. 이벤트는 별도의 스레드에서 처리되므로 실행 시간에 대한 보장은 없다.
이벤트는 강력한 기능이다. React Native 컴포넌트에 대한 참조 없이도 컴포넌트를 변경할 수 있기 때문이다. 하지만 이벤트를 사용할 때 주의해야 할 몇 가지 문제점이 있다:
- 이벤트는 어디서든 보낼 수 있으므로, 프로젝트에 스파게티 코드 같은 의존성을 초래할 수 있다.
- 이벤트는 네임스페이스를 공유하기 때문에 이름 충돌이 발생할 수 있다. 이러한 충돌은 정적으로 감지되지 않아 디버깅이 어려울 수 있다.
- 동일한 React Native 컴포넌트의 여러 인스턴스를 사용하고 이벤트 관점에서 이를 구분하려면, 식별자를 도입하고 이벤트와 함께 전달해야 한다. 네이티브 뷰의
reactTag
를 식별자로 사용할 수 있다.
React Native에서 네이티브 함수 호출하기 (네이티브 모듈)
네이티브 모듈은 자바/코틀린 클래스로, JS에서 사용할 수 있다. 일반적으로 각 모듈의 인스턴스는 JS 브리지마다 하나씩 생성된다. 이 모듈들은 임의의 함수와 상수를 React Native로 내보낼 수 있다. 자세한 내용은 이 글에서 확인할 수 있다.
경고: 모든 네이티브 모듈은 동일한 네임스페이스를 공유한다. 새로운 모듈을 생성할 때 이름 충돌에 주의해야 한다.