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()
また、これまで書いた性質ゆえに、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を生成するようにしている。
実際のReact Nativeでは別スレッドでJavaScriptが実行されているという制約もあり、UIViewとはまた別にUIViewと対応する構造体RCTShadowViewを作っている。今回の例ではJavaScriptの実行もメインスレッドでやっているのでその辺は省略した。
Viewの表示
RCTUIManager.createView()
だけではViewが生成されても、実際に表示はされない。実際にRCTRootViewへとmountするためには、ReactUpdates.batchedUpdates()
中にコンテナとなる要素へmountするよう、ネイティブモジュールに対して命令しなければならない。ここでもRCTUIManagerがつかわれている。RCTUIManager.manageChildren()
で先ほど登録したViewの付け外しをハンドリングしている。
React Nativeを拡張するときはRCT_EXPORT()
マクロを書くとあるが(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
- React NativeはまだAndroidに対応していないが、構成を踏襲すれば実装できる
- 異なる言語がインターフェースする部分は小さく保たれている
- Rhinoでは案の定遅すぎるのでV8あたりが要る