こんにちは、開発本部の高井です。メドレー開発本部で行われている勉強会「TechLunch」でReact Nativeについて発表しました。
私は普段はSwift、Kotlin/Javaを使ってネイティブアプリを開発しており、React Nativeに触るのは初めてでした。そこで今回は、アプリエンジニアの視点から、実装するための基本的な知識と弊社の実際の開発で使えそうかを検討した結果についてご紹介します。
なぜReact Nativeを触ってみようと思ったか
オンライン診療アプリ「CLINICS」の開発では、iOS/Androidアプリをそれぞれのネイティブ言語で別々に開発しているため、実装やレビューの際にはプラットフォーム間の仕様の違いを理解する必要があり、なかなか大変だと感じていました。
これらの課題に対してこちらのブログでも紹介したような施策を行って改善を行ってきましたが、ソースを共通化することでより開発効率を向上できないかと思い、クロスプラットフォーム開発についても調べてみることにしました。
その中でも以下の理由から、今回はReact Nativeについて調べてみることにしました。
- JavaScript(以下JS)・Reactのため、Webエンジニアがネイティブアプリ開発を行う際のハードルを低くすることができそう
- UIの実装にネイティブUIを使用しているので自然なデザインやインタラクションを作りやすそう
- ある程度リリースから時間が経って情報が豊富にある
特に弊社では、ネイティブアプリよりWeb開発をメインに行ってきたエンジニアの方が多く、またWebフロントにReactが使われているプロダクトもいくつかあるので、React Nativeを採用することでチームの開発効率の向上だけでなく、開発本部全体でもネイティブアプリ開発の学習コストが低くなるのではないかと考えました。
そこで、以降ではネイティブアプリエンジニアとWebエンジニアそれぞれにとって、開発しやすいかどうかという観点でReact Nativeの開発方法を見ていきたいと思います。
初期設定について
インストール〜アプリ実行
インストールは公式のGetting Startedにもありますが、以下のコマンドで完了です。
$ brew install node
$ brew install watchman
$ npm install -g react-native-cli
今回、私が触るにあたってはXcodeとAndroid Studioのシミュレータ(エミュレータ)で実行しながら開発しましたが、React NativeはXcodeやAndroid Studioがインストールされていなくても、Expoというクライアントアプリを実機にインストールすることで画面をプレビューしながら開発することができます。
これなら、Xcodeのダウンロードを待つ必要もありません!(iOSエンジニアでもアップグレードのたびにXcodeのダウンロードを待つのはイライラします)
次に、プロジェクト作成、シミュレータでのアプリ実行は以下のコマンドで実行できます。
ただし、シミュレータ実行前にXcode Command Line ToolsのインストールとAndroid Studioでいくつかの設定(SDKのインストール、AVDの作成、環境変数の設定)が必要です。
# プロジェクト作成 $ react-native init AwesomeProject # アプリ実行(シミュレータ) $ cd AwesomeProject $ react-native run-ios or react-native run-android # Androidの場合、emulatorを別途起動してからでないと実行できない
実装してみた感想
UIの実装方法
React NativeではReact同様UIの各パーツをコンポーネントと呼び、それらを配置することでUIを実装していきます。違いはWebのHTMLの代わりにNativeのUIを描画するためのコンポーネントとして使う点です。
見た目やレイアウトはCSSと似たような形式で記述します。React Nativeで使えるスタイルのプロパティは各コンポーネントで異なりますが、例えば、Viewコンポーネントに設定できるプロパティには以下のようなものがあります。
Webで使われているものと全く同じというわけではないですが、flex、margin、borderなどの使い慣れているCSSのプロパティ名で設定できるので、Webエンジニアにとっては実装のハードルが下がるのではないかと思いました。ただ、普段CSSを触っていないネイティブアプリエンジニアにとっては学習コストがかかると思います。
また一度ビルドすれば、JSによる修正内容をビルドなしでシミュレータに反映することができるので、View周りの調整は効率的にできそうでした。
// js/components/home.js const renderItem = ({ item, index }) => ( <View style={styles.row}> <Text style={styles.title}> {parseInt(index, 10) + 1} {". "} {item.title} </Text> <Text style={styles.description}>{item.description}</Text> </View> ); const styles = StyleSheet.create({ row: { borderBottomWidth: 1, borderColor: "#ccc", padding: 10, }, title: { fontSize: 15, fontWeight: "600", }, description: { marginTop: 5, fontSize: 14, }, });
ただ、OSごとにデザインを合わせる方針にしない限りは、コードも別々になるところがわりと多くなりそうだと感じました。
例えば、TabBar(iOS)とDrawerLayout(Android)、DatePicker(iOS)とTimePicker(Android)、ProgressView(iOS)とProgressBar(Android)などはReact Nativeでは別コンポーネントとして提供されていて、APIも違っていました。
画面遷移
画面遷移(プッシュ、モーダル、タブ遷移など)のためにAPIとして公式で提供されているのはiOSのみでAndroidは別途、実装するか、サードパーティのライブラリを使用する必要があります。
わたしが使ってみたreact-native-navigationはモーダル、プッシュなどの遷移がネイティブAPIベースで実装されているので、ネイティブ言語で実装した場合と比べて違和感なく実装することができました。
下記のような形でプッシュやモーダル表示での遷移ができます。特定の画面に戻る機能については開発中であったり、機能的な制約は少しありそうです。そういう細かいところはネイティブ言語でやった方が自由が効くので、良いなと思います。
それでも、iOSとAndroidでは複数画面の管理や遷移についての考え方が違い、Androidを初めて開発した時に同じことを実現するのが難しかった覚えがあるので、共通の方法で実現できるのは便利でした。特にWebだとあまり画面間の遷移について考えることはないと思うので、共通化されているとネイティブアプリ開発の学習コストが下がると思います。
this.props.navigator.push({ // プッシュ screen: 'example.PushedScreen', title: 'Pushed Screen' }); this.props.navigator.pop({ // 前の画面に戻る animated: true, animationType: 'fade', });
this.props.navigator.showModal({ // モーダル screen: "example.ModalScreen", title: "Modal", animationType: 'slide-up' });
ネットワーク周り
ネットワーク経由でデータを取得して、モデルに変換し、アプリ内で使うというよくある操作を行うにはFetch APIを利用します。Fetch APIはJSで提供されているPromiseベースのAPIです。例えば、以下のような形でJSONを返すAPIからデータを取得し、receiveHelthNews
関数にオブジェクトに変換した配列を渡すことができます。JSのAPIなのでWebエンジニアにとっては使いやすいのではないかと思います。
ネイティブ言語でそれぞれ実装する場合は、URLSession(iOS)とHttpURLConnection(Android)、あるいは各プラットフォーム向けに提供されているサードパーティのライブラリなどを使うと思いますが、当然、APIは異なるのでそれぞれの実装方法を把握しないといけなくなります。それに比べると学習コストは低くなりそうです。
// js/actions/index.js export function fetchHelthNews() { return dispatch => fetch(constructHealthNewsUrl()) .then(response => response.json()) .then(json => dispatch(receiveHelthNews(json.articles))) .catch((error) => { console.log(error); }); }
ネイティブアプリ特有の機能について
その他のアプリ開発でよく使うネイティブアプリ特有の機能を実装する方法は以下のようになります。多くの機能がサードパーティのライブラリに依存しているので、各言語のバージョンアップ時の対応が少し心配ではありますが、よく使う機能については実現することができます。
- Push通知:AndroidはハンドリングするAPIが公式で提供されていないのでサードパーティのライブラリを使う
- カメラ、キーチェーンアクセス/ユーザデフォルト:公式のAPIは提供されていないのでサードパーティのライブラリを使う
- 位置情報の取得:公式APIが提供されている
- ディープリンク:アプリ起動時にハンドリングを行うAPIは提供されている。ユニバーサルリンクやIntentFilterの設定は各プラットフォームで個別に必要になる
リリースはどうやるか
アーカイブやストア配布についてはネイティブアプリの配布と同じプロセスになります。
各プラットフォームで証明書等の設定を行い、XcodeやAndroid Studio、あるいはCLIでコマンドを実行して、ipa / apkファイルを作成し、各Storeにアップロードする必要があります。
CIはBitriseなどが使えます。Bitriseでビルドを試してみましたが、React Nativeのリポジトリと接続したときにできるデフォルトのワークフローを使えば、同時に2プラットフォームのアーカイブが作成できて便利でした。
あと、まだ試せてはいないのですがCodePushを使えば、審査に提出することなしに既存のアプリを変更することもできるらしいので、非常に便利だと思いました。
どんな場合にReact Nativeを採用できそうか?
React Nativeで開発することで、Web開発者の視点で見るとプラットフォームのネイティブ言語で開発するよりも、だいぶ学習コストが下がるのではないかと思いました。またサードパーティのライブラリを使えば、機能的に大きな問題となるようなことはなさそうでした。ただ、画面遷移のライブラリがそうだったように、もしやりたいことができないという場合は、妥協しないといけない部分が出てきそうだと思いました。
一方、アプリエンジニアにとっては、慣れるまではかなり開発速度が下がりそうなのでデメリットも大きいかなと思いました。ReactとJS、またReduxなども新たに理解しつつ開発していたので結構ハードルが高いと感じました。開発環境もビルドが通ってるうちは、View周りの調整がすぐに確認できて良かったのですが、ランタイムエラーになるなどで、シミュレータがリロードできなくなった場合に再度ビルドし直すということがよく起こり、常に快適に開発できるというわけではありませんでした。
運用面でみると一通りアプリ開発に必要そうなツールは揃っているし(クラッシュ監視、CI、テスト配信、リリース)、Code Pushなど便利なツールもあるので利点が多いと思いました。
結論としては、Webエンジニアが社内に多かったり、開発チームにReact、JSが得意なメンバーがいるなら、実際の開発でも使えるかなと思いました。ただ、アプリエンジニアにとってはかなりストレスがたまるプロジェクトになりそうだと感じました。
まとめ
自分の現状で考えると、アプリは得意だけどJSやReactにそこまで詳しくないので、もし直近のプロジェクトで難易度もそこそこ高いようであれば、正直あんまり使いたくないなあという気持ちがあります。ただ、技術の幅を広げるとか、組織全体のポータビリティなどの観点で考えると利点が結構あるのかなと思います。チャンスがあればぜひ、チャレンジしてみたいです。
わたしはどちらかというと技術や開発ツールの新しさよりも、ユーザとの接点のところで新しいことや面白いことを追求したいと思っていますが、開発効率や品質の向上のために最適なものを選択できるように、今後も新しいツールのキャッチアップは積極的に行っていきたいと思っています。
お知らせ
メドレーは、5/31-6/2に開催されるRubyKaigi 2018にLTスポンサーとして協賛します。ブースも構えておりますので、イベントにお越しになる方は、ぜひブースにも遊びにいらしてください!