FEエンジニアがReact Nativeを触ってみました

はじめまして、mediba FEエンジニアの楊です。 最近猫パンチ避け上手になっているので、猫を困らせています。

React Native初見

react-native
ネットで調べてみて、第一印象は「可愛いかった」です。
その他に感じた印象は下記です。

  • facebookのcross-platformフレームワークで、1回書いたらAndroid、iOS、Web全部動くだろう。
  • React Native(以下RN)という名前付けなので、React開発者にとって、使いやすいだろう という浅い認識で、チームメンバーと一緒にRNのプロジェクトを書いてみました。

React Native振り返る

ある程度コードを書いてみると、以下の事に気が付きました。

  • バージョンアップが早い
    • 最近のリリースを見ると1年間3個のメインバージョンがもうリリースされて、RNのバージョン管理が課題になりそう、特にキャッチアップには、互換性の問題が目の前に
  • debugの難しさ
    • Web開発と比べると、RNでよくみる真っ赤な画面のエラー画面あるが、それのメッセージだけで、フロントかネイティブかのエラーの判断は難しい
  • 要素の高さがスクリーンの高さを超えたらスクロールできない
    • Webだと生まれつきでスクロールできるけど、RNの場合一番外側にスクロール可能のエレメントでwrapしてあげないと
  • iOSとAndroidにおいての違い
    • ある程度RNがその違いを埋めてくれてますが、それぞれの環境に依存した処理がある(外部libで済む場合も)
  • リストの選択
    • RNのリスト、元々listViewとflatList二つもあって、listviewは性能的な問題で放棄されて、flatListが推奨されてます(実際カルーセルを作った際に困ってた)

つまり、RNは依存心の強い子でした。

印象的ポイント

RNを触ってみて、一番興味深いところというと、native codeで書いたSDKを扱える部分でした、いわゆるNative Module Bridgingというものです。 前述の通りある程度RNがiOSとAndroidの違いを埋めてくれてますが、それぞれのOSに依存したAPIはJSでの扱いは不可なものもあり、あるいはより高いパフォーマンス的、マルチスレッド的に作りたい場合、native moduleを介してそれを実現できます。

実際手を動かして、 公式ドキュメントをみながら、自分なりに簡単な「hello-world」を作ってみました。(学校でJavaを学んでたので、個人的に親切なAndroidを選んだ^^)

ステップ:

  • 環境設定
  • なんとなく動くnative code書く
  • RNを介して、native moduleとして、JS側にに披露(expose)する
  • JS側で呼び出し

環境設定

公式ドキュメントに沿って環境設定できます。JavaScript(以下JS)、TypeScript(以下TS)両方作れますが、 自分はTSのテンプレートを作りました(TS最高)

なんとなく動くnative code書く

環境設定終わりましたら、下記のandroidのフォルダで作業をしていきます(自分はJavaの構文あまり自信ないから、Android Studioでandroidフォルダーを開いてコードを書いているw)

android/app/src/main/java/com/awesomeprojectの配下でHelloModule.javaという新規Javaファイルを作ります。 ファイルの中身はこんな感じ:

public class HelloModule extends ReactContextBaseJavaModule {
    private static ReactApplicationContext reactContext;

    HelloModule(ReactApplicationContext context) {
        super(context);
        reactContext = context;
    }
    public static void setTimeout(Runnable runnable, int delay){
        new Thread(() -> {
            try {
                Thread.sleep(delay);
                runnable.run();
            }
            catch (Exception e){
                System.err.println(e);
            }
        }).start();
    }

    @ReactMethod
    public void sayHelloToPopUp(String name) {
        Toast.makeText(reactContext,"Hello World,"+name,Toast.LENGTH_LONG).show();
    }

    @ReactMethod
    public void sayHelloAfterThreeSecond(String name, Promise promise) {
        setTimeout(()->promise.resolve("Hello after 3 seconds,"+name),3000);
    }

    @NonNull
    @Override
    public String getName() {
        return "HelloModule"; //この命名でNativeModulesに追加
    }
};

とりあえず試しに、下記二つの関数を作ってみました。

  • AndroidのToastを使って出力できるsayHelloToPopUp
  • Promiseを返すsayHelloAfterThreeSecond

App(JS側)に披露(expose)する

init後に自動生成されたandroid/app/src/main/java/com/awesometsproject/MainApplication.javaの中身を見てみると

 protected List getPackages() {
          @SuppressWarnings("UnnecessaryLocalVariable")
          List packages = new PackageList(this).getPackages();
          // Packages that cannot be autolinked yet can be added manually here, for example:
 ※        // packages.add(new MyReactNativePackage()); ←この行を解放
          return packages;
        }

既に用意してくれたPackage名でpackageを作ります。上記のファイルと同じフォルダー配下でMyReactNativePackage.javaのファイルを作成し、nativePackage作り、NativeModuleに先程作成したHelloModuleを追加すれば完成です。

public class MyReactNativePackage implements ReactPackage {
    @NonNull
    @Override
    public List createViewManagers(@NonNull ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }
    @NonNull
    @Override
    public List createNativeModules(
            @NonNull ReactApplicationContext reactContext) {
        List modules = new ArrayList<>();
        modules.add(new HelloModule(reactContext));
        return modules;
    }
}

JS側で呼び出し

最後の一歩は、App.tsxで使うことですね。   import { NativeModules } from 'react-native';   まずNativeModulesをimport,それから先程作ったsayHelloToPopUpsayHelloAfterThreeSecondを呼び出します!

  const { HelloModule } = NativeModules
  const sayHello = React.useCallback((words)=>{
    HelloModule.sayHelloToPopUp(words)
  },[])
  const sayHelloThreeSecondsLater = React.useCallback(()=>{
    HelloModule.sayHelloAfterThreeSecond('lalalalala').then((res)=>sayHello(res))
  },[])
...
<button title="{"> 

最後、rootでyarn androidすれば、起動を待ち、シミュレーターが立ち上がってアプリインストール完了、アプリでpressボタンをポチる 3秒後に添付画像のトーストが出てきます img

感想

RNのようなクロスプラットフォームのフレームが宣伝したように、「一回書いたらどのプラットフォームでも動ける」という理想があるが、 実際にプロジェクトを作るに当たって、ネイティブアプリもフロントを全部理解し、一人でのiOS,AndroidとWeb全てのコードを書けることはありえない。 しかしRNを架け橋に、ネイティブチームとフロントチームを連携し、斬新なビッグ(B.I.G)フロントチームを生み出せるではないでしょうか。 ことわざの通り「理想なき者に成功なし」^^