React Native互換でAndroidのViewを描画してみた

[内部実装から追うReact Native](http://qiita.com/Misumi_Rize/items/127349c7dc422261c5ff)の続き。

React Native互換でAndroidにViewを描画するところまでできた。
https://github.com/MisumiRize/ReactNativeSandbox

結果的にほとんどReact Nativeの写経になってしまったが単純化のために以下は省略している。

* props, state
* style
* event
* 別スレッドでのbundle実行
* 複数のRCTRootView

JavaScriptCoreが組み込まれていないAndroidでも手軽に扱えるJavaScript処理系としてRhino上で動かしているが、とてももっさりしている。

## DOM無効化

ReactComponentをrenderするには`ReactUpdates.batchedUpdates()`を呼び出せばよい。しかし、`require('react')`するとデフォルトでDOMを操作する処理が付加されてしまう。DOM操作はReact Nativeには不要なので、まずDOM機能を無効化しなければならない。

通常のReactモジュールは`ReactDefaultInjection.inject()`で各種DOM処理やインターフェースに対する実装を登録しているので、これらの代わりにDOM処理のない実装を登録しなければならない。`ReactUpdates.batchedUpdates()`を実行する前に最低限登録しておかなければならないのは、以下の二つである。

* `ReactUpdates.injection.injectReconcileTransaction()`
* `ReactUpdates.injection.injectBatchingStrategy()`

https://github.com/MisumiRize/ReactNativeSandbox/blob/master/src/ReactAndroid/ReactAndroidDefaultInjection.js

また、これまで書いた性質ゆえに、React NativeとしてReactの各処理を呼び出せるようにするには、Reactが持つ各機能をReactとは別に`require()`して`ReactDefaultInjection.inject()`だけは実行せずインターフェースを登録しなければならない。この例ではrenderするために必要な最小のインターフェースしか登録していないが、実際のReact Nativeはもっと多くのインターフェースが登録されている。

https://github.com/MisumiRize/ReactNativeSandbox/blob/master/src/ReactAndroid/index.js
https://github.com/facebook/react-native/blob/3c4341084396a56d66b9ebc9fb0be1d315cd43ff/Libraries/ReactIOS/ReactIOS.js

## ReactNativeComponent

React Nativeの各Componentは`render()`するとReactIOSNativeComponentのElementを返す。ReactNativeComponentはMixinが定義されていて、ReactデフォルトのReactCompositeComponentではなくこのMixinの`mountComponent`, `receiveComponent`, `unmountComponent`が発火するようになっている。mountには関係ないだろうと思って`receiveComponent`を省略するとMixinの定義が発火せずはまる。

ReactNativeComponentはmountされた際、RCTUIManagerというネイティブモジュールにViewを生成、登録するよう、`RCTUIManager.createView()`を呼び出している。引数としてViewのクラス名が渡されるので、このクラス名に基づいてObjective-C側でViewを生成するようにしている。

https://github.com/facebook/react-native/blob/3c4341084396a56d66b9ebc9fb0be1d315cd43ff/Libraries/ReactIOS/ReactIOSNativeComponent.js#L253

実際のReact Nativeでは別スレッドでJavaScriptが実行されているという制約もあり、UIViewとはまた別にUIViewと対応する構造体RCTShadowViewを作っている。今回の例ではJavaScriptの実行もメインスレッドでやっているのでその辺は省略した。

## Viewの表示

`RCTUIManager.createView()`だけではViewが生成されても、実際に表示はされない。実際にRCTRootViewへとmountするためには、`ReactUpdates.batchedUpdates()`中にコンテナとなる要素へmountするよう、ネイティブモジュールに対して命令しなければならない。ここでもRCTUIManagerがつかわれている。`RCTUIManager.manageChildren()`で先ほど登録したViewの付け外しをハンドリングしている。

https://github.com/facebook/react-native/blob/3c4341084396a56d66b9ebc9fb0be1d315cd43ff/Libraries/ReactIOS/ReactIOSMount.js#L140

React Nativeを拡張するときは`RCT_EXPORT()`マクロを書くとあるが（[React Nativeサイト](http://facebook.github.io/react-native/)のExtensibilityの項目参照）、`RCTUIManager.createView()`や`RCTUIManager.manageChildren()`はまさにこの方法でJavaScriptから呼び出せるようになっている。React NativeではRCTBridgeでネイティブとJavaScript間の橋渡しが行われている。互換実装ではこの辺の手間を省くために、Rhinoでグローバル変数としてRCTUIManagerを定義してJavaオブジェクトを渡した。

## まとめ

ここまで実装することで、Android Viewを表示できる。
https://github.com/MisumiRize/ReactNativeSandbox/blob/master/exampleapp/index.android.js

![Screenshot_2015-03-31-22-09-51.png](https://qiita-image-store.s3.amazonaws.com/0/66350/dab57a00-e976-5713-d2ec-797302f01736.png)

* React NativeはまだAndroidに対応していないが、構成を踏襲すれば実装できる
* 異なる言語がインターフェースする部分は小さく保たれている
* Rhinoでは案の定遅すぎるのでV8あたりが要る