これから ReactNative を使うときに抑えておいた方がいいこと

この記事は「[React Native Advent Calendar 2020](https://qiita.com/advent-calendar/2020/react-native)」の12日目の記事です。(忙しくて当日夜になってしまいました、すいません)

## 自己紹介

バックエンドのエンジニアとしてそろそろ８年になります。
元々、C#, Java, PHPなどでサーバーアプリケーションの構築に関わっていましたが、

最近になり、アプリ開発を行う機会も何度かありました。その中で ReactNative 開発案件に
２度ぶつかり、その中で「これが分かってたら、もうちょっとうまく開発できていたんじゃないか？」
と思うことがあり、これから ReactNative でアプリ開発を行う人向けに情報記載していきます。

## なぜ ReactNative にエンカウントするのか...

ReactNative に関わる場合というのは、自分は大きく下記のパターンかと思っています。

- JavaScript(Vue.js等)でフロント開発をやっていた
- Reactでフロント開発をやってた
- Android/iOSでアプリ作っていた

自分は、「Android/iOSでアプリ作っていた」でした。
なお、自分の周りでは大半が「JavaScript(Vue.js/jQuery等)でフロント開発をやっていた」でした。

しかし、ReactNative で安定して開発するためには下記の知識が必要だと思っています。

- React 知識
- コンポーネント設計
- React native パッケージ
- iOS/Androidアプリ構築に関する知識

それぞれ、どんなことを抑えていればいいかという所感を記載します。

## React 知識

自分は React をよく知らないまま、開発に入りました。
なんかよくわからないけど、`React.Component` を継承してコンポーネント作っていけばいいんだな！
そこまでは既存コードを見れば、何となく分かりました。で Props と State が変化すれば画面も切り替わっていく。

しかし、思った通りのタイミングで描画ができません。
render メソッドが呼ばれないタイミングもあれば、逆に呼ばれまくって処理が重くなったり。
そのときの私は、ライフサイクルを理解していませんでした。

[React component ライフサイクル図](https://qiita.com/kawachi/items/092bfc281f88e3a6e456)
（一部メソッドが廃止になってます: [React.js のライフサイクルメソッド componentWillReceiveProps の廃止対応](https://qiita.com/mori-dev@github/items/c0a458384b5390494784))


描画の問題はなんとかなりました。しかし、今度は画面間のデータ連携が上手くいかなくなります。
前の画面で入力されたデータを次のページに連携することは可能です。react-navigation を使ってパラメータを渡せば。
[rearct native react-navigation　個人的メモ](https://qiita.com/kaba/items/46cb5fa3baa64c9fd9fd)

しかし、２つ前の画面で設定されたデータを画面で表示するにはどうすればいいんだ..ということになり、
どこかにグローバル変数的にデータを持たせておき、参照するようにしました。
しかし、その定数を別画面が変更しても、Component 内のデータが変わっていないため、描画に変更が走りません。

どうすればいいんだー...と探し回り、出会ったのが、Redux でした。
[Redux入門【ダイジェスト版】10分で理解するReduxの基礎](https://qiita.com/kitagawamac/items/49a1f03445b19cf407b7)

概念を理解するのは少し苦労しましたが、とっても便利です！
しかし、Redux では Reducer に「APIを呼び出す」ことをしてはいけない..と書かれています。
API の結果で状態を変えたい時はどうするんだ..とたどり着いたのが redux-saga というミドルウェアでした。(同じようなミドルウェアに redux-thunk, redux-promise があります)
[redux-sagaで非同期処理と戦う](https://qiita.com/kuy/items/716affc808ebb3e1e8ac)
[React + Reduxにおける非同期処理](https://qiita.com/ryokkkke/items/d3a0375ef51448d345ed)

ここまで分かって、実装である程度のことができる...と自信を持てるようになりました。


## コンポーネント設計

一番最初、画面単位でコンポーネントを作っていました。（複雑だったら、分割すればいいよねー的なノリ）
そして、部品をコンポーネント化していくのですが、メンテナンスが上手くいかなかったです。

- 似たようなコンポーネントの二重管理になる
- コンポーネントがどのコンポーネントから使われているかわからない

### 似たようなコンポーネントの二重管理になる

２つの画面に似たものがあるけど、画面が違うことによって微妙に挙動が異なる。
そうなったときに、別にしてしまっていて、二重管理が発生している。

### コンポーネントがどのコンポーネントから使われているかわからない

画面ごとに微妙にコンポーネントが違ってたりするときに、ある程度共通化するために
コンポーネントを作っていくと、コンポーネントの構造が全体として歪になります。
そうすると、コンポーネントがどのコンポーネントを使っているのか...ということが分かり辛くなります。

### どうすればいいのか？

そこで、調べてみると、Atomic デザインというものに到達します。
[Atomic Designを使ってReactコンポーネントを再設計した話](https://blog.spacemarket.com/code/atomic-design%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6react%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%82%92%E5%86%8D%E8%A8%AD%E8%A8%88%E3%81%97%E3%81%9F%E8%A9%B1/)

しかし、定義自体が明確ではなく、本当に考えずに適用すると苦しみます。
まず、Atom, Molecules, Organisms, Templates, Pages という５階層が基本ですが、
全ての画面をこのように組めというわけではありません。現実的には全て５階層で組むとかなり冗長な画面も発生します。

また、Atom は、最小単位ということで理解しやすいですが、
Molecules, Organisms の差はなんぞや？...ということが一番の問題です。
これに関しては、自分は明確に切ることができなかったので、

- Organisms: それ単体でも業務的に意味のあるコンポーネント
- Molecules: Atom を組み合わせてはあるけど、業務的に意味のないコンポーネント

という風に Molecules を Atom として作っておきたいけど、分離したものも必要だ！という場合に使っていました。
とはいえ、そんなケースを扱うことはほぼなかったので、Molecules はほとんどなかったです。

これにより、メンテナンス可能なアプリを作る自信が出てきました。

## React native パッケージ

ReactNative では、様々な有名なパッケージがあります。例えば

- [React Native Elements] (https://reactnativeelements.com/)
- [React Navigation](https://reactnavigation.org/)
- [
react-native-camera](https://github.com/react-native-camera/react-native-camera)
- [react-native-snap-carousel](https://github.com/archriss/react-native-snap-carousel)

この辺りのパッケージを知識として持っておけば、コストを最小にできると思います。
（また、出来るだけ要件をパッケージ側で実現できるように寄せられるとなお良し）

しかし、中には パッケージが管理されていないものもあります。
仮にそういったものを使うと、現バージョンの ReactNative で動くかどうかの検証、
いざ何か起こった時の対応というのがリスクとして存在します。

このため、昔の記事でよく紹介されていても、放置されているものは正直使い辛いです。
そんな時は作ってしまうのも一つの対策だと思います。

自身の経験では、評価する時の星(Rating)を画像に載せて欲しい、そのときに背景を透過色にして欲しい
という要望があったんですが、[React Native Elements](https://reactnativeelements.com/docs/rating/)では対応できず、古いパッケージもちょっと怖い。
..ということで、自身でコンポーネントを作ったことがあります。

## ReactNative バージョンに関する話

ReactNative バージョン上げるときは、結構覚悟を持ってやった方がいいです。
ReactNative は後方互換性は保証しません。また、そこそこ破壊的だと自分は思っています。

自分は(2019/8月ごろ)Androidの64bit対応が必要になり、バージョン(v0.57→v0.60にした)をあげました。（開発開始からAndroidアプリがアップロードできなくなるまで放置されていた）
コンパイルエラー出ていないか、機能が壊れていないか、デザインはどうか、iOS側に影響はないか...。

[ReactNativeのAndroid 64bit対応時に読んでおきたいメモ](https://qiita.com/hand-dot/items/8c29bd4fc382bfba717b)

こちらのバージョンあげた理由は、「Androidアプリは64bitアプリしかできなくなった」
..っていう、理由でしたが、このように、ReactNative 上の理由以外にも、Apple, Google のポリシー変更によってバージョン上げざるを得ないことがあります。

古いバージョンで開発していると、大変な影響を受けることになります。
長くメンテナンスをするのであれば、バージョンアップはこまめにやりましょう...。


## iOS/Androidアプリ構築に関する知識

ここまでは TypeScript で各部分のことに関して記載してきましたが、デプロイに関する話です。
まず、iOSアプリをリリースするのであれば、iOSの証明書周りは複雑ですが必須です。

[[iPhone] iOS, Certificate 証明書を作ってみる](https://i-app-tec.com/ios/apply-application.html)

あと、顧客にテストビルドを見てもらうには下記の方法があります。

- adhoc 証明書によるビルド
- TestFlight を使う

ただし、TestFlight を使うには、AppleStore にアプリ登録が必要なので、
そこまでしたくない場合は、「adhoc 証明書によるビルド」を使うことになります。

Apple 側では、このようなテストアプリを不特定多数に提供されないためか？
起動できるDeviceIDの設定を証明書に含めています。この辺りを知らないと
提供したアプリが動かない、という事態に陥ります...。

基本的に Expo で作ってあれば、これ以降はあまり自分は嵌りませんでした。

Expo では、証明書は作ってくれますし、iOSアプリへの変換・Androidアプリの変換は
Expo側で行ってくれるので、この辺りの面倒ごとがかなり改善されます。

なのでもし、React Native CLI で開発する場合 (もしくは Expo で reject して開発する場合)は、

iOSのビルドに関する知識が必要です（xcode設定, Cocoapod, Build設定など）
また、Androidのビルド(gradle)に関する知識も必要です。

もし、ReactNative 側に package がインストールされていなかったりすると、
iOSアプリのビルド時にリファレンスエラーが出たりするので、ビルドエラーが起こったときは結構苦労します。

なので、自分は Expo 以外の開発は現在はほぼ行っていません。
ReactNative CLI を使うケースとしては、Expo で対応できない..というケースですが、
一般的なアプリはほぼそういった要件はありませんので。

仮にあったとしても、要件次第では Native アプリ(Swiftで)作ってかもしれません。

## まとめ

ReactNative におよそ1年少し関わって、すごく苦労した話を中心に書きました。

ReactNative のいいところは、１つのソースコードから２つのプラットフォームを作れる手軽さです。
スモールスタートでアプリを作っていきたい、といった要望にはマッチすると思います。

パフォーマンスを求められるもの、最新技術を用いたい場合には、使い辛いですが、
選択肢の一つしては、これからも関わっていきたいと思います。