💍

Ubieが2024年にReact Nativeを選ぶ理由

2024/11/18に公開8

Ubieでは、Ionic(Capacitor)でガワアプリ的に実装されていたモバイルアプリ(Android/iOS)を、2024年初頭にReact Native (with Expo)にリプレイスしました。

「なぜ今更React Nativeを?」という方もいらっしゃると思います。本記事では、UbieにとってReact Nativeがフィットした理由や検討した点を紹介します。Ubieの技術資産、人材、事業展望などのコンテキストを前提とするものであり、一般的な技術の良し悪しを論じる記事ではないことに注意してください。

Full-Stack TypeScript

Ubieのプロダクト開発チームでは、フロントエンドエンジニア/バックエンドエンジニアといった技術領域での担当分けは原則せず、プロダクト開発エンジニアとして企画から開発、分析などに一貫して携わっています。

このように仕事を広く持つ前提では特に、なるべくコンテキストスイッチを減らしてフロー効率を最大化したいです。そのため、Webフロントエンドはもちろん、バックエンドにも(基盤サービスを除いて[1])Node.jsを採用し、言語をTypeScriptに統一する技術戦略をとっています。これにより、社内の環境整備やイネーブリングの投資についても、スタートアップの限られたリソースをTypeScriptに集中させることができています。

TypeScriptに統一された状態を維持したまま、"ガワアプリ"を超えた高品質なアプリを開発できる React Nativeは魅力的でした。

また、UbieではWebアプリの資産が大きいため、部分的にWebViewを介してWebアプリを流用しています。これもTypeScriptに揃っているおかげで、アプリ~WebView間で直感的な型付きのRPCを実現することができています。

この観点ではIonicも有力な選択肢です。TypeScriptで実装できることに加え、全体が1枚のWebViewベースのためWebアプリ資産の流用もしやすく、最速で走り出すことができます。Ubieでもその点を評価して初期リリースではIonicを採用していました。

逆にFlutter WebやDartバックエンドを用いたDart中心戦略、あるいはCompose Multiplatformを用いたKotlin中心戦略もあり得るとは思いますが、UbieではWebアプリからグロースした経緯もあり、React Native/TypeScriptに寄せることにしました。

WebのReactとのシナジーは薄い

逆に、Webアプリ側で利用しているReact(Next.js)とのシナジーにはあまり期待していません。

技術資産の観点では、Web側で React Native for Web を利用しているわけでもないので、コンポーネントの共通化などはできません。一応Expo 52で入ったReactDOMサポートを使うことはできますが、これは単にビルド時に解決したHTMLをWebViewに描画するものであり、カジュアルに重ねるとメモリ消費が大変なことになるため、現実的な利用シーンはかなり限られると考えています。これは上述の通りWebView1枚で表現しているIonicに優位性がある点です。

ナレッジの観点では、コンポーネント設計をはじめとする React way な考え方は一定引き継げるでしょう。しかし、Webとアプリでは画面設計やナビゲーションの考え方が根本的に違う(例: 線形な履歴スタック V.S. パラレルなビュー)ため、考え方を切り替えなければならないシーンも多いです。

総じて、すでにWebアプリがある状態でモバイルアプリを導入するシナリオにおいては、TypeScriptであることのシナジーはありますが、Reactであることのシナジーはそこまで大きくないと考えています。

開発体験

2020年頃まではかなり大変だったReact Nativeですが、近年は大きく改善しています。Expo以前のReact Nativeで疲弊した経験のある方は、騙されたと思ってExpoを触ってみてほしいです。例えばReact Nativeのアップデート作業は、Changelogを隅から隅まで読んで、ネイティブコードなどの差分をうまく反映して、依存パッケージのバージョンをうまく合わせて、、、と重たい作業でした[2]。これは現在、ExpoのContinuous Native Generation(CNG)[3]の成熟で解消しました。

CNGはExpoプロジェクトからiOS/Androidプロジェクトを自動生成してくれる仕組みです。これによりネイティブコードを自分たちで管理する必要がなくなります。React Nativeのバージョンアップに必要なネイティブコードの修正なども巻き取ってくれるため、基本的には次の手順だけで対応できます。

  1. npm install expo@latest でExpoを更新
  2. npx expo install --fix でプラグインなどを適切なバージョンに更新
  3. npx expo-doctor@latest で整合性が取れていることを確認

Ubieではまだ2度のバージョンアップしか経験していませんが、今のところ特段の追加対応は発生していません。

とはいえ完全にネイティブコードが隠蔽されて触れない状態だと困りますよね。Ubieでも、ヘルスコネクトのような新しい機能をいち早く利用するためにネイティブコードのパッチが必要でした。その場合もExpoプラグインを自作することでネイティブコードやビルドファイルなどに変更を加えることができますから、難なく対応できています。

もちろんReact Nativeに妥協している点もあります。

まず、UIはFlutterが作りやすいと感じます。豊富な組み込みウィジェットがありますし、画面描画もImpeller(or Skia)ベースの独自実装であるためOSの差異が出ません。React Nativeでは、組み込みコンポーネントが極めてプリミティブなものしかなく、多くを3rd partyから選定したり自作したりする必要があります。画面描画はネイティブのビューを呼び出す仕組みのため、OS間で意図しない差異が出て困ることもそれなりにあります。

差異については、社内のデザインシステム Ubie Vitals に準拠した実装を用意することで、できる限り吸収したいと考えています。

また、ここ数年でExpoなどが急速に進化していることの裏返しとして、ドキュメントやブログ記事などの前提が古い場合が多くあります。調べ物がしにくいのもそうですが、特にLLMが古い実装を出すことがあり困っています。

これは嬉しい悲鳴ではありますから、Ubieからも積極的に情報公開をして貢献していきます。

ユーザー体験

「React Nativeといえば遅い」というイメージを持たれている方もいらっしゃると思います。この点についても、近年は2つの大きな改善が加わっています。

まず、2022年にJavaScriptエンジンがJavaScriptCoreからHermesに切り替わりました。

JavaScriptCoreはSafariやBunで使われているエンジンで、Bunの速さの秘訣になっているくらい性能が良いものの、JITコンパイルを前提とするため起動時には最高性能が出ません。これはブラウザやサーバーのようなワークロードには適していますが、モバイルアプリでは起動の遅さがユーザー体験を毀損していました。

この問題を解決すべくReact Native向けに開発されたエンジンがHermesです。Hermesでは、事前に(ビルド時に)JavaScriptコードをHermesバイトコードにAOTコンパイルします。これにより、Mattermostアプリを用いたベンチマークでは起動時間が半分以下になっています。

2つ目は、New Architectureと呼ばれる内部アーキテクチャの刷新です。

旧アーキテクチャでは、画面描画やネイティブAPIの実行に必要なネイティブ側とのやり取りにBridgeと呼ばれる機構を用いていました。Bridgeは、リクエスト/レスポンスをJSONにシリアライズして非同期に通信します。このシリアライズや非同期通信が大きなボトルネックになっていました。

新アーキテクチャではBridgeが廃止され、代わりにJavaScript Interface(JSI)が導入されます。JSIでは、ネイティブのC++と同期的にメモリ参照を共有し、メソッドを呼び出せるようになります。これによりシリアライズや非同期通信を排除することができ、大幅なパフォーマンス改善につながっています。

こういった改善の結果、DiscordをはじめとするリッチなアプリもReact Nativeで実現されていることから、パフォーマンス面で技術的な制約は発生しないだろうと判断しています。

さらに、TypeScriptの型情報を用いてネイティブの機械語にAOTコンパイルするStatic Hermesというプロジェクトも進行しており、「React Nativeといえば速い」といえるような未来がくるのではないかと期待しています。

パフォーマンスの問題が解消すれば、React NativeがiOS/AndroidネイティブのUIを描画していることは、むしろ操作性の観点で優れています。

FlutterやIonicはネイティブアプリっぽい操作感やトランジションを独自実装で再現しています。よく頑張ってくれているとは思いますが、特にUbieが以前使っていたIonicは、言語化しにくい「アプリっぽくなさ」が目立つ印象でした。対してReact NativeはネイティブのUIそのものですから、特に工夫することなく、慣れ親しんだネイティブアプリの操作感を得ることができます。

全体から見ると些細な問題かもしれませんが、ユビーのユーザーは体調が悪い状態でアプリを使ってくれています。そんな状況においては小さな違和感でもストレスに繋がってしまうため、こだわりたいポイントでした。

OTAアップデート

Ubieでは、高速なデリバリーとフィードバックループをとても重要視しています。1週間のスプリントの中でも準備ができたものはすぐにリリースし、新たに学習できたことからそのスプリントのバックログをリアルタイムに更新しています。参考までに、Webアプリのフロントエンドは週に20-30回ほどデプロイされています。

この開発スタイルをモバイルアプリ開発にも持ち込むためには、あらゆる変更でストア審査を待つわけにはいかず、ストア規約に違反しない範囲での Over-The-Air(OTA) アップデートが必須でした。

React Nativeでは、Expoが提供する EAS Update によってOTAアップデートが実現できます。EAS Updateはプロトコル仕様が公開されており、スケールしてコスト影響が大きくなった際に内製化を選択できることも魅力的です。公式のサンプル実装もあります。

React Nativeのアーキテクチャにおいては、本番アプリでもJavaScriptエンジン(Hermes)を用いてアプリケーションコードを実行し、そこからネイティブの描画やロジックが呼び出されます。HermesにはAhead-of-Timeコンパイルがありますが、これはあくまでJavaScriptコードをHermesのバイトコードにコンパイルするだけで、ネイティブの機械語まで落とすわけではありません。

言い方を変えると、ランタイムでHermesバイトコードのインタプリタが動いているということですから、OTAアップデートによるアプリケーションコードの差し替えを無理なく実装できます。

比較対象として、FlutterにもShorebirdというOTAアップデートサービスがあります。こちらは2024年4月にv1.0が出たばかりで、EAS Updateと比べるとアーリーフェーズですが、Flutterのファウンダーが立ち上げたプロジェクトなので期待値が高いです。

ただ、Flutterアプリの本番ビルドでは、Dartで書かれたアプリケーションコードも機械語にAOTコンパイルされます。これはFlutterのパフォーマンス面での優位性を支えてきた一方で、ランタイムで機械語を差し替えることは難しいですし、ストア規約にも違反するため、長年OTAアップデートの実現を阻んでいました。Shorebirdでも一筋縄ではいかず、Flutter Engineをフォークしたり、OTAアップデートされた部分のみをインタプリタで実行するようにしたり、と野心的に工夫しているようです(参考記事)。この挑戦には技術者としてめちゃくちゃワクワクしつつも、オリジナルのアーキテクチャやエコシステムとの乖離が激しく、業務利用するにはやや不安が残ります。

これからFlutterのアップストリームとの統合なども含めてShorebirdが定着することも十分に考えられますが、現時点では、技術的な成り立ちも含めて安心して利用できるという点でReact NativeとEAS Updateを評価しています。

Ionicは単なるWebViewですから、React Nativeと同様にOTAアップデートが実現できます。Appflowや、Capgoなどのサービスがあり、特にOSSであるCapgoはセルフホストもできますから、React Native/Expoと同条件といえます。

React Server Components

技術選定した2024年初頭の時点で、すでにNext.js App RouterでのRSC対応は実用段階でした。一方でReact Nativeに関しては、ほぼ情報がない状態でしたが、近い将来に対応されるだろうと期待していました。

モバイルアプリでもRSCが使えるようになれば、いよいよBFFを作る必要がなくなり、App Routerと合わせてシンプルなアーキテクチャを構成できます。これは今のところReact Nativeにしかできないこと[4]で、十分に賭ける価値がありました。

https://x.com/koichik/status/1825711304834953337

RSCは明らかにOTAアップデートのあるべき姿であり、Expoの主力ビジネスであるEAS Updateの体験を大幅に向上させます。さらにいえば、RSCに対応するということはサーバーを持つということであり、Expoにとっては新たにホスティングサービスを展開する大きなビジネスチャンスになりそうです。こういったビジネスとしてのExpo社の視点でも投資する意味があるだろうと考えました。

そしてつい先週、ExpoのRSC対応がベータ版で出ました。実用段階まではまだ時間がかかるかもしれませんが、ベットした未来が実現されていくのは技術選定の醍醐味ですね。

https://expo.dev/blog/universal-react-server-components-developer-preview

エコシステム

React NativeはMetaが主導しているプロジェクトで、Facebookアプリ等に利用されています。最近発表されたMeta Quest向けのアプリもReact Nativeで実装されているようです。

それだけでなく、MicrosoftやAmazon、Shopifyといった体力のあるテック企業が、OutlookやTeams、Amazonアプリなど主力製品にReact Nativeを採用しています。直近のNew Architectureへの移行といった大プロジェクトも、MetaだけでなくMicrosoftやExpo、Callstackなど複数企業が雇用するコアコントリビュータの協力で推進されています。このようにGAFAMレベルの会社を中心としつつも分散したコミュニティによって、エコシステムが継続的に改善されていることを高く評価しています。

https://reactnative.dev/showcase

Flutterも、主導するGoogleに加えて、AlibabaやTencent、ByteDanceなどの中国系テック企業が積極的に採用していて安心感があります。

しかし開発体制はGoogleの比重が大きく、2024年5月時点ではおよそ85%がGoogleからなんらかの形(雇用など)で資金提供を受けているようです。アルムナイも含めるとさらに多いでしょう。今なおGoogleが大きく投資し、一貫して製品管理をしてくれているのはFlutterの強みである一方で、4月のFlutterチームを含むレイオフで不安が広がるなど、イチ企業の意思決定でコミュニティが揺れることに危うさは感じます。

ただ、直近はコミュニティフォークであるFlockが登場しました。これを機にコミュニティベースの開発が促進されると状況は改善しそうですね。

https://flutter.dev/showcase

Ionicは、Web技術という意味では最もオープンで安定したエコシステムといえるでしょう。しかし、Ionicそのものに大きく投資しているテック企業はあまりありません。

代わりに個人のコミュニティプラグインを広く取り入れてエコシステムを育てていますが、ボランティアワークの側面が強いためメンテナンスが滞ってしまうものも多いです。実際、Ubieのリプレイス前のIonic製アプリではいくつかのプラグインをフォークしてメンテナンスを引き継ぐ必要がありました。

https://ionic.io/showcase

総じてReact Nativeは、巨大資本による投資とコミュニティによる貢献のバランスが比較的よいと感じています。

人材採用

上述のようにグローバルではReact Nativeもそれなりに盛り上がっていますが[5]、日本においては圧倒的にFlutterが優勢だと感じます。Flutterは求人・経験者のどちらもよく目にする一方で、React Native経験者をドンピシャで採用することは難易度が高いです。

Google Trendでグローバルと日本のトレンドを比較すると、明確に差があります。検索クエリのトレンドでしかないので参考程度ですが、特に日本においてReact Nativeが厳しい戦いなのはわかると思います。

Google Trendのスクリーンショット。グローバルでFlutterの人気度が78に対して、React Nativeは42
グローバルのトレンド

Google Trendのスクリーンショット。日本でFlutterの人気度が78に対して、React Nativeは16
日本のトレンド

一方で、高品質なアプリを作っていく上でReact Native特有の難しさが支配的かというとそうではなく、結局はモバイルアプリ開発の造詣をもってアプローチする課題が大半を占めると思います。そのため、モバイルアプリ開発に取り組んでいる方であれば、React Nativeの経験がなくても十分にご活躍いただけると考えています。ただし、モバイルアプリエンジニアの中には昔のReact Nativeの開発体験にトラウマを持たれている方も多く、再度React Nativeの方を向いてもらうために払拭していかねばならないなとも思います。

加えて、社内のWebエンジニアたちがアプリへの挑戦に前向きでいてくれることなども加味し、新規採用の観点では現時点でFlutterの方が有利だと思いつつも、中長期的なReact Nativeの可能性にベットしています。というかUbieとして日本のコミュニティを盛り上げていく意気込みです。

おわりに

UbieがReact Nativeを選定した理由を紹介しました。

この技術選定が正解だったと言えるよう、一緒にモバイルアプリ開発とReact Nativeエコシステムの盛り上げをしてくれる方を大募集しています。下記募集かXのDMでご連絡いただけると嬉しいです。

https://herp.careers/v1/ubiehr/bjpV04rFZw3f

https://herp.careers/v1/ubiehr/wIsgYrS5GcNS

脚注
  1. プロダクト開発チームが殆ど触らないような基盤サービスはGoで開発している ↩︎

  2. 途中でUpgrade Helperが登場したが、それでも手作業が多く辛かった ↩︎

  3. 以前まではManaged Workflowと呼ばれていた ↩︎

  4. 厳密にいえばRSC相当の実装をすれば他フレームワークでも可能だが、かなり距離がありそう ↩︎

  5. 「北米だとReact Nativeの方が人気」的な言説もよく見かけるが、これは出典を見つけられていない ↩︎

Ubie テックブログ

Discussion

guruguru-dekiurkoguruguru-dekiurko

とても素晴らしい記事でした。
OTA アップデートについて、補足があった方が良さそうだなと思うことがあり、恐縮ですがコメントを…🙏

ストア規約に違反しない範囲での Over-The-Air(OTA) アップデート

「ストア規約に違反しない範囲」とは、どこまでの範囲を指すのでしょうか?
後に記述されている「ランタイムで機械語を差し替えること」でしょうか?
明示されている箇所がないので、各々の解釈に委ねられています、というのが正直な回答になるのだとは思いますが…😢

Microsoft Code Push の READMEShorebird の FAQ では、OTA の仕組みでダウンロードできるのは Interpreted code だから OK という解釈です。ただ、それによる変更は節度を守ってね、的な記載がどちらも添えられています。
Apple Developer Program license agreementの 3.3.1 B または C に引っかかる可能性は否定できず、ぼかすしかなさそうな事情はありそうです(深く追ってはないのですが…)

現状で EAS Update のストア規約に違反しない主な使い方というのは、FAQ の以下に集約されていると考えます。

EAS Update is a great way to quickly deliver improvements to the people who use your apps. For example, consider an app that has a critical bug that needs to be fixed. With EAS Update you can quickly get the fix out and later follow up with a new submission that includes the fix built in.

また Shorebird の FAQ を見ても、想定される用途はパッチやバグフィックスがメインのように思えます(最後の項目はなんだかぼかしてるのが気になりますが…)。

Emergency fixes to production apps.
Shipping bug fixes to users on older versions of your app.
Shipping constantly (e.g. every hour).

Zenn ですとツルオカさんの以下の見解が、現状では自分も無難なものだと思いました。
https://zenn.dev/tsuruo/articles/13687d8455d1b1#アプリストアのガイドラインに抵触しないのか

本記事でもユースケースやガイドラインに触れましたが、あくまでもパッチ適用(緊急の不具合修正)用途に限定して、機能開発などは従来通りアプリストア経由での配信にするのが想定される活用方法だと思います。

Yuku KotaniYuku Kotani

詳細にコメントいただきありがとうございます!

「ストア規約に違反しない範囲」とは、どこまでの範囲を指すのでしょうか?
後に記述されている「ランタイムで機械語を差し替えること」でしょうか?
明示されている箇所がないので、各々の解釈に委ねられています、というのが正直な回答になるのだとは思いますが…😢

おっしゃる通り解釈の問題であり、ツルオカさんの見解はまさに無難な解釈だと思います。

さらに言えば、インタプリタというのも解釈の広い言葉ですよね。
極論、CPUを機械語インタプリタと捉えれば、「ランタイムで機械語を差し替えること」も問題ないと言い張れるかもしれません(技術的にはサンドボックスを超えたメモリアクセスが不可能そうですが)。

総じて、ストア側もOTAサービスプロバイダ側もあえてぼかしているであろう領域なので、ストア側がこの規約によって守りたいことに想いを馳せて「怒られないようにやる」しかないんだと思ってます。答えにならない回答ですみません。

guruguru-dekiurkoguruguru-dekiurko

EAS Updateはプロトコル仕様が公開されており、スケールしてコスト影響が大きくなった際に内製化を選択できることも魅力的です。

めちゃくちゃ nits ですが、プロトコル仕様(Expo Updates v1)は EAS Update ではなく Expo Updates のものだと思います。

また、それによって内製化を選択できることは EAS Update 自体の魅力ではないと思うのですが、いかがでしょうか?
EAS Update は若干コストが高く、とにかくめんどうな OTA を投げるか、自前で抱えるかというのは最初からトレードオフになる気がします(多くのサービスにも言えることですが…)。
途中で EAS Update をやめてインフラを内製化することで、開発コストと長期的なメンテナンスコストが発生する時、それがペイするか?を考えるのも若干難しいです。

将来的にスケールした場合でも、他のサービスに乗り換えるか、多少割高でも EAS Update を使い続けよう…という話にはなりそうです😇

Yuku KotaniYuku Kotani

こちらもありがとうございます!

めちゃくちゃ nits ですが、プロトコル仕様(Expo Updates v1)は EAS Update ではなく Expo Updates のものだと思います。

そうですね、厳密には Expo Updates のプロトコルに準拠したサービスプロバイダが EAS Update ですね。

また、それによって内製化を選択できることは EAS Update 自体の魅力ではないと思うのですが、いかがでしょうか?

Expo Updates というプロトコルとクライアント実装が EAS Update から分離されていることで、後からプロトコル準拠のOTAサーバを内製するだけで差し替えられることは魅力だと思っています。

おっしゃる通り、EAS Updateと内製にはコストのトレードオフがあります。初期的にはEAS Updateの従量課金も少ないですから、内製コストはペイしないですよね。ただ、Expo Updatesプロトコルはそこまで難しいものでもないですし、参照実装も公開されているため、スケールして従量課金が膨らんだ場合には十分に内製を検討する余地があると考えています。

もちろん内製コストは社内の様々なコンテキストに依存しますから一概には言えませんが、大事なのは、SaaSとプロトコル/クライアントの分離によって、スケール後に内製を検討する余地が現実的に残っていることだと思います。

王文雄王文雄

capacitorをやめた理由は、エコシステムの弱さ、プラグインのアップデートの厳しさなどになりますでしょうか。(あとはネイティブと比べたときの速度差とか?)
capacitorをやめた理由をもうちょっと詳しく知ることができたら嬉しいです。

Yuku KotaniYuku Kotani

コメントありがとうございます!

まさにエコシステムの弱さは主要な理由の1つです。

レンダリングのパフォーマンスについては、Webも十分に速いため問題にはなりませんでした。
ただ、WebView1枚の表現力に限界があり、アプリに求める体験(下タブごとにスタックを持ち状態が維持されるなど)をエミュレートしきれないため、どうしても「アプリっぽくない」体験が残ってしまいました。
独自スタック管理とかView Transition API とかで頑張ることもできると思いますが、無理に頑張るよりは、自然に実現できるネイティブに寄せちゃおう、という判断です。

王文雄王文雄

ありがとうございます。大変参考になりました。

Yuku KotaniYuku Kotani

一応補足すると、すでにWebアプリがグロースしてる状態で、モバイルアプリの可能性を検証する手段としては最高でした。過去に戻ってやり直すとしてもCapacitorで始めると思います!