ReactNative 本体のバージョンのアップグレードを行う


会社プロジェクトを 0.44.3 (2017年6月時点の最新版)から 0.49.5(2017年11月頭時点の、最新版のstable)に上げた。思ったより大変だったのでメモっておく。

# そもそも何故 ReactNative のバージョンを上げるのか？

RN はまだまだ絶賛進化中で、本体のバージョンが上がることで React / React Native のパフォーマンスの向上や、周辺環境の向上、例えば Flow での型チェックがさらに進化するなど恩恵を受けれる。

実際にアップグレードしてCIの実行速度も20%ほど向上した。以下は CircleCI のビルド時間の中央値の値。真ん中あたりでガクッと下がったのがRNのバージョンを上げたタイミング。

![Kobito.bDYD6B.png](https://qiita-image-store.s3.amazonaws.com/0/5330/be3e0372-c022-1c1c-f91a-d3de965428ec.png "Kobito.bDYD6B.png")


また、破壊的な変更が本体に入ることも多く、例えば RN 0.47 から Android ネイティブライブラリ作成で利用されていた[createJSModules](https://github.com/facebook/react-native/releases/tag/v0.47.2) が削除されたため、それ以前の RN では最新の第三者が公開しているネイティブライブラリの利用がRN本体のバージョンを上げない限り、古いライブラリを使い続けることになる。

そのため、定期的に適切なタイミングでバージョンを上げることが望ましい。

なお、2017年11月頭時点では、毎月に0.1づつバージョンが上がっていくリリースを取っている。

## 定期的に適切なタイミングとは？

最新版は最終マイナーバージョンが出るまでは割とバグっている(特に 0.N.0 などの出たばかりの最新版)ので、最終のマイナーバージョンが出る(= 次のバージョンの最新版が出る)までは更新を待った方が良い。

また新しめのバージョンの RN を使うと、破壊的な変更によって、そもそも今使っているライブラリが上手く動かなくなることもしばしばある。

そのため、最新版のNヶ月前の最新版(Nはお好きな数字を)ぐらいにアップデートするのが、良い感じに周辺ライブラリも対応していて、ハマりどころも少ないと思う。

# バージョンを上げるときに見る情報

- https://github.com/facebook/react-native/releases

こちらのリリースのログの、 Breaking changes の破壊的変更箇所を見て、なにが変わったかを理解してから上げると、問題解決がし易い。

# バージョンを上げる

## react-native-git-upgrade

`react-native-git-upgrade` という npm ライブラリがあるので、これ経由でアップグレードをする。ただ、一気に 0.44 -> 0.49 など大幅にバージョンアップすると一筋縄でいかないので、0.1 づつ上げていくと良い。以下のようにバージョンを指定しつつ上げられる。

```
$ react-native-git-upgrade 0.49.5
```

何事も無く適用できると楽なのだが、ネイティブビルドが必要なライブラリを入れてると、Xcode 関連でコンフリクトし易い。コンフリクトすると、失敗した旨と、以下のディレクトリらへんに上手く適用できなかった patch ファイルがはき出されるので、それを見つつ暖かみのある手パッチをしていく。

```
/var/folders/fd/*/T/react-native-git-upgrade
```

## rn-diff を見つつ上げる

この記事を書くまで知らなかったのだが、

- [Updating Your React Native App – React Native Training – Medium](https://medium.com/react-native-training/updating-your-react-native-app-a724c996a76d)

にあるとおり、以下のレポジトリに、`react-native init` で作ったアプリの各 RN のバージョン間の差異を diff としてコミットしてくれている。

- https://github.com/ncuillery/rn-diff

そしてほとんどの場合、package.json の react-native のバージョンアップデートと、.flowconfig のバージョン修正がほとんどという驚愕の事実。なるほど〜。

これらの修正見つつ手パッチした方が、react-native-git-upgrade で上手くいかなかったときにどうにか頑張るより全然簡単そう…！

たとえば、今回行った RN 0.44.3 -> RN 0.49.5 の差異は

- https://github.com/ncuillery/rn-diff/compare/rn-0.44.3...rn-0.49.5

> Showing  7 changed files  with 69 additions and 19 deletions.

なので、この変更を上手く適用すれば、ReactNative 本体にまつわる修正は適用することが出来る。また diff を見ると

- index.ios.js が index.js へと entryFile に変更された
- project.pbxproj で libRCTxxx 系がいくつも変更された

当たりが解る。ただ、本当の変更内容では index.android.js が削除され index.js になった、などテンプレ生成される .js 関係の変更はこの diff に入れてないようなので、その辺は注意が必要そう。

# ReactNative の本体のバージョンアップを実際にして問題修正

ちまちまと。

## project.pbxproj

前述の `project.pbxproj` のバージョンを上げるのが、手パッチではシンタクスエラーを起こしてしまい、かつ Xcode が何も言わず落ちるのでつらたん。

ただ xcodeprojer を使うと、パースに失敗する箇所を発見できたので、その後は楽に対応できた。

- https://qiita.com/hotchpotch/items/214ca98104eaa0472df2

## npm ライブラリのアップグレード

`yarn upgrade-interactive` コマンドで、必要なライブラリを適宜インタラクティブなUIで更新。べんりコマンドだった。

## ネイティブモジュールを使ったライブラリのコンパイルが失敗する問題

```
react-native unlink package
react-native link package
```

で再度 link することで、新しいバージョンで加わった設定が追加される。これで大抵通る。

## react-native link packagename 失敗する問題

`Maximum call stack size exceeded` でエラーが出る場合。

[Maximum call stack size exceeded when running react-native link · Issue #12188 · facebook/react-native](https://github.com/facebook/react-native/issues/12188#issuecomment-303608010)

手動で`project.pbxproj`にパッチを当てた状態で発生。上記リンク先コメントの通り、Xcode でプロジェクトファイルを開いて、プロジェクトファイルを更新したら発生しなくなった。Xcodeで保存し直すことで、`project.pbxproj`の細かな正規化が行われる模様。

## 自前ネイティブモジュールの実装変更

Breaking changes を見つつ対応。

自前で書いていた Android のネイティブモジュールを `createJSModules` を使わない書き方へと変えて Android コンパイルを通るように変更。これは簡単。

## ESLint  の対応

RNバージョンアップと関係ない話しだけど、何を血迷ったのか、ESLint 関係もRNバージョンアップと同じタイミングで上げてしまった。かなりの lint エラーが出るようになったのでちまちま対応したが、本来 RN のバージョンアップと ESLint のバージョンアップは関係ないので、別のタイミングでやるべきだった。

## Flow の対応

これが一番大変だった。RN 0.48 -> RN 0.49 で Flow の 0.53 を使うようになり、React での Flow の書き方が大幅に変わった。

- [Even Better Support for React in Flow – Flow – Medium](https://medium.com/flow-type/even-better-support-for-react-in-flow-25b0a3485627)

よーしパパ flow 実行しちゃうぞー。の実行結果。

```
$ yarn run flow
中略
... 1286 more errors (only 50 out of 1336 errors displayed)
```

エラーですぎワロタ ^o^ ^o^ 〜〜〜。

しかしながら、`flow-upgrade` という Flow 0.53 にアップグレードを適用してくれる cli の素敵ライブラリがあったので、それを実行。

- https://github.com/facebook/flow/tree/master/packages/flow-upgrade

最初 npm (yarn) で入れただけでは上手く動かなくて悲しみに暮れたけど、プロジェクトに一時的につっこんで

```
$ yarn add flow-upgrade
$ yarn run flow-upgrade
```

で上手く動いたので良かった。これで Flow のエラーは 1200 -> 600 個ぐらいに減った ^o^… ので、あとはちまちまちまちま...修正した。

なお、新しいよりよい React での Flow の Props, State の型指定 `class MyComponent extends React.Component<Props, State> {}` は、解りやすいとおもう。

- https://flow.org/en/docs/react/components/

子ノードの指定も解りやすくなった。

- https://flow.org/en/docs/react/children/

ちまちま修正は面倒くさかったけど、Flow より良くなっている感はあった。

## テストの修正

500個弱ぐらいのテストケースが今はあるんだけど、スナップショットテスト以外はそこそこ通ったのでちまちまと修正する。スナップショットテストも React Native Component の差異が解るので、問題無い物は適宜 -u で更新書けつつテストを通した。

Flow の修正に比べたら、全然楽だった。

## アプリを動かして修正

実際に動かしてみると、細かい挙動や画面レイアウトが一部修正が必要だった。でも合計で3,4つほどだったので、こちらはすぐに終わった。

また、将来大きく非互換な修正が入るような変更は、warning が出るのでそれもちまちま直した。例えば Image は子要素を許可しない、使いたい場合は ImageBackground を使ってね、など。

# アップグレードの感想

Flow の対応が思ったより大変だった。他はそんなに大変でなかった。ユーザへアプリリリース後も特にクラッシュや新たなバグは出ずに動いているしてる模様。

Flow 、大変だったけど型がある安心感はあるので、大体きちんと書いていて良かった。RN アプリにおいて ES6 で書く場合は Flow 書かないという選択肢は無いと思う。またテストもサクサク書けるので、こちらも書いておいて良かった。Flow とテストがないとアップグレード大変そうなので、みんな書こう！！

また、

> そのため、最新版のNヶ月前の最新版(Nはお好きな数字を)ぐらいにアップデートするのが、良い感じに周辺ライブラリも対応していて、ハマりどころも少ないと思う。

この"Nヶ月前の最新版"、のNは今のところ2-3が適正値なのかなーとなんとなく思った。

----

仕事で React / ReactNative 関連の仕事をしてみたい、そんなあなたは[コチラ](https://www.wantedly.com/projects/84748)。