書いた人間はCとC++からの類推解釈でObjective-C読めるけれど、iOSのAPIはちゃんと理解していない。Sampleはすでに動かした前提で書く。
TL;DR
document
に依存しないVirtualDOM (= React.js)だけであれば、iOSでもJavaScriptCore上で実行可能なので、React.jsをそのまま投げ込める。
index.ios.bundle
AppDelegate.m
にindex.ios.bundle
へのURLが定義されている。エミュレータで起動した際、同時にhttpサーバが立ち上がっているのはindex.ios.js
をbundleしたものをこのURLとして返すためにある。
ところで、React.jsを使う大原則として、React ElementをrenderするコンテナにはReact.jsが管理しているDOM以外のDOM(htmlで書いたものとか)を配置することができない。この大原則はReact Nativeでも生きていて、RCTRootView
の中にはReact Native以外の方法でUIを配置することはできない。
一方で、RCTRootView
の中に配置されている要素がすべてReact Nativeの管理下にあるならば、bundleしたJavaScriptを読み込み直してRCTRootView
の中身を空にすることで、アプリ自体を再ビルドすることなくUIのリロードが可能になる。エミュレータ実行時にCmd + R
でJavaScriptの変更が反映できるのはこのような仕組みが働いていることにある。
JavaScriptCore
URL指定されたJavaScriptコードは読み込みの過程を経てここで実行される。RCTJavaScriptExecutor
自体は@protocol
として宣言されているが、デフォルト扱いで実装されているRCTContextExecutor
を見る。
- (void)executeApplicationScript:(NSString *)script
sourceURL:(NSURL *)url
onComplete:(RCTJavaScriptCompleteBlock)onComplete
{
RCTAssert(url != nil, @"url should not be nil");
RCTAssert(onComplete != nil, @"onComplete block should not be nil");
[self executeBlockOnJavaScriptQueue:^{
JSValueRef jsError = NULL;
JSStringRef execJSString = JSStringCreateWithCFString((__bridge CFStringRef)script);
JSStringRef sourceURL = JSStringCreateWithCFString((__bridge CFStringRef)url.absoluteString);
JSValueRef result = JSEvaluateScript(_context, execJSString, NULL, sourceURL, 0, &jsError);
JSStringRelease(sourceURL);
JSStringRelease(execJSString);
NSError *error;
if (!result) {
error = RCTNSErrorFromJSError(_context, jsError);
}
onComplete(error);
}];
}
- (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block
{
if ([NSThread currentThread] != _javaScriptThread) {
[self performSelector:@selector(executeBlockOnJavaScriptQueue:)
onThread:_javaScriptThread withObject:block waitUntilDone:NO];
} else {
block();
}
}
ざっくり言えば、別スレッドでJavaScriptCoreを使って読み込んだJavaScriptコードを実行している。JavaScriptCoreはブラウザやDOMに依存するdocument
を扱うことはできないが、React.js含むVirtualDOMは実際にレンダリングするまでは単なるオブジェクトの操作なので、document
やDOMを使わず読み込みが可能。document
に依存しないiOSネイティブなUIを代わりに呼び出せば、React.jsからiOSのUIも呼び出しができるということである。
まとめ
React Nativeは、コンセプトにあるとおりiOSのUIをReact.jsと同じ方式で組み立てることに主眼が置かれている。
- React.jsの作法をiOSのUI設計に適用できる
- React.jsの作法に従うことでスマートUIのようなバッドパターンをある程度避けられる
- 当然React Nativeで管理しているUIをReact.js以外で触ることはご法度
- フロントエンドでこれまで使用していたツールセットが利用できる
React.jsをパターンとして捉えることで、上記のような特徴を得られる。APIのほとんどが2014年頃のJavaScriptスタンダードを逸脱していないので、Titanium Mobileのような「あのAPI何だっけかな」的なフレームワークそのものへのロックインを避けることはできる。
代わりにTitanium MobileのようなObjective-CとiOS APIを抽象化してJavaScriptで扱えるような仕組み自体はないので、「JavaScriptだけでアプリが作れる」風便利フレームワークではない。