본문 바로가기

리액트 네이티브

wix/react-native-navigation 코드 분석

react-native cli로 앱을 생성하면 아래와 같은 index.js 파일이 생성됩니다.

import {AppRegistry} from 'react-native';
import {name as appName} from './app.json';

AppRegistry.registerComponent(appName, () => App);

 

AppRegistry.registerComponent는 입력받은 컴포넌트를 모듈 내부의 Runnables 객체에 등록합니다.

https://github.com/facebook/react-native/blob/v0.75.3/packages/react-native/Libraries/ReactNative/AppRegistry.js#L66-L127

 

Runnables 객체에 여러 개의 Runnable 등록할  있지만, react-native cli로 만들어진 앱은 무조건 (app.json에 정의된 앱 이름)mainComponentName 앱만 로드하도록 되어 있습니다.

public void onCreate(Bundle savedInstanceState) {
  mReactDelegate = new ReactDelegate(/* ... */);
  if (mainComponentName != null) {
    loadApp(mainComponentName); // app.js에 등록한 앱 이름
  }
}

protected void loadApp(String appKey) {
  mReactDelegate.loadApp(appKey); // ReactRootView 객체 생성, ReactContext 생성
  getPlainActivity().setContentView(mReactDelegate.getReactRootView());
}

https://github.com/facebook/react-native/blob/v0.75.3/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java#L132-L139

 

그리고 단 하나의 ReactRootView 객체만 관리(mReasctRootView)하며, 해당 객체를 MainActivity의 컨텐츠로 설정하고, JS에선 해당 ReactRootView의 태그를 얻어와서 여기에 Runnables에 등록한 단 하나의 컴포넌트를 그립니다.

https://github.com/facebook/react-native/blob/v0.75.3/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactDelegate.java#L32-L292

 

react-native cli로 만든 프로젝트에서 MainActivity MainApplication ReactActivity ReactApplication을 상속하는한 선택권은 없습니다.

 

wix/react-native-navigation는 이러한 제한을 풀었습니다.

 

설치 과정을 보면, MainActivity, MainApplication에서 라이브러리에서 제공하는 NavigationActivity NavigationApplication를 상속하도록 변경하여 내부 구조를 완전히 바꿔버렸습니다.

https://wix.github.io/react-native-navigation/docs/installing#2-update-mainactivityjava

 

Runnables에 다수의 컴포넌트를 등록해 놓고,

// 스크린 등록
Navigation.registerComponent('Home', () => Home);
Navigation.registerComponent('Settings', () => Settings);

 

Navigation.registerComponent는 내부적으로 AppRegistry.registerComponent를 사용합니다.

https://github.com/wix/react-native-navigation/blob/8c64969f09657918e5840dca4f9ab8162f3bf356/lib/src/adapters/AppRegistryService.ts#L5

 

스크린 이동시 라이브러리에서 제공하는 ReactView(ReactRootView 상속)를 생성하고, 해당 뷰에 Runnables 객체에서 컴포넌트를 추출하여 그립니다.

// 스크린 이동
Navigation.push(props.componentId, { 
  component: {
    name: 'Settings',
  },
});

https://github.com/wix/react-native-navigation/blob/master/lib/android/app/src/main/java/com/reactnativenavigation/react/ReactView.java#L24

 

즉, 하나의 스크린이 하나의 앱이 되는 것입니다.

 

이와 같은 방법으로 기존 네이티브 프로젝트에 리액트 네이티브를 통합하는 것도 가능합니다.

 

리액트 컴포넌트를 Runnables에 등록해 놓고,

https://reactnative.dev/docs/0.75/integration-with-existing-apps#2-add-your-react-native-code (마지막 라인)

 

CatalystInatnace를 통해 자바스크립트에 접근(참고: 리액트 네이티브에서 JS와 Java가 통신하는 방법)해서 Runnables에서 컴포넌트를 얻어와 View에 그리고, 해당 View를 화면으로 쓰면 되는 것입니다.

https://reactnative.dev/docs/0.75/integration-with-existing-apps#the-magic-reactrootview (mReactRootView.startReactApplication 부분)

 

위의 방법은 제가 코드를 분석할 때 최신 버전인 0.75.0이고, 0.76.0에선 더 간단하게 앱을 불러 올 수 있습니다.

https://reactnative.dev/docs/integration-with-existing-apps

 

리액트JS로 표현하면 다음과 비슷합니다.

<!-- index.html -->
<body>
  <div id="home"></div>
  <div id="settings"></div>
  <script src="index.js"></script> 
</body>

// index.jsx
import React from 'react';
import ReactDOM from 'react-dom';

const Home = () => {
  return <h1>첫 번째 React 앱</h1>;
};
const Settings = () => {
  return <h1>두 번째 React 앱</h1>;
};

const root1 = ReactDOM.createRoot(document.getElementById("home"));
const root2 = ReactDOM.createRoot(document.getElementById("settings"));

root1.render(<Home />);
root2.render(<Settings />);

 

각 스크린은 하나의 리액트 루트 노드가 할당된 별개의 리액트 앱이기 때문에, 몇 가지 귀찮은 설정이 필요할 수 있습니다.

등등..

 

제대로 사용해 본 적은 없지만, 정말 빠를 것으로 예상됩니다.

 

하나의 앱으로 개발하다보면 처음엔 빨랐는데, 트리 깊이가 점점 깊어지고, 앱의 규모가 커질 수록, 새로운 스크린을 만들 때 뭐 별로 추가하지도 않았는데, 이상하게 느린 것 같은 느낌이 듭니다.

 

원인을 찾기도 점점 힘들어집니다.

 

하지만 코드만 보면 wix/react-native-navigation는 스크린을 추가할 때마다, 처음 리액트 네이티브 앱을 만들고 실행시킬 때 느끼는 빠릿빠릿함을 느낄 수 있을 것으로 예상됩니다.