1.8K Views
October 07, 24
スライド概要
https://react-native-meetup.connpass.com/event/329486/
プロテインの摂取を頑張る
副業で3年関わっている React Nativeプロジェクトで 実際に効果があったこと3選 IMANAKA, Kouta @ React Native Meetup #18 ft. CureApp
今中幸太と申します ● 株式会社Techouse 所属(本業) ● 製造業向けメディア事業と、製造業向けSaaSをやっている会社 ● ○ ひらたく言うと、自動車工場など向けの転職支援サイト ○ ひらたく言うと、超大企業版のナントカ HRみたいなやつ テックハウスではFlutterやってます... ○ ● 「ジョブハウス工場」というアプリを手がけています Flutter 4年目、RN 6年目くらい もともとAndroidの人(10年戦士)、x-plat越しにiOSも触る
副業で3年 React Native やってます ● かつて勤めてた会社でフルタイムで3年、 その後副業で3年React Nativeをやり続けている ● 「パラレル」というボイスチャットアプリ ● 本業優先のため、週末副業で週8h稼働 今日は副業先でやってきた改善のうち 効果があったことを3つご紹介します
パラレル 〜 友達と遊べるたまり場アプリ とは? > パラレルは、友達とゲーム・動画・音楽などのコンテンツを > 一緒に楽しむことができる『オンラインのたまり場アプリ』です。 https://play.google.com/store/apps/details?id=com.reactcorp.cocalero ● 通話しながらゲームで遊べる ● おすすめのゲームは「とび夫」 ● iOSはネイティブ実装。AndroidはReact Native (Expo未使用 ) ● アプリの性質上、Native実装されている要素が多め
注意! ● 「3年やってて効果があったこと」を語ると言ったが、 「いますぐ効果があること」を語るわけではない ○ ● どちらかというと過去を回顧する発表です(エモ〜) あまりコードは出てこない ○ Lintのコードとかは見せられるものもあるかもなので、 気になったら後で直接聞いてください
① Hermes対応 ● 2022年12月着手、2023年7月頃マージ ● この対応はRN0.69から0.71へのアップデート対応と同時に行った ● ○ 0.70: Hermesのデフォルト ON ○ 0.70: React JSX Transform ○ 0.71: TypeScriptの型定義提供 ○ 0.71: gapサポート ○ 0.71: 0.69で消えたPropTypesがしれっと帰ってくる事件 ○ 0.71: AsyncStorage と MaskedViewIOS の削除 様々な対応をしてなんとかリリースにこぎ着けた
Hermesで高速化!・・・と素直にはいかない ● Hermesを有効化したアプリの動作が、 Hermesを無効化したアプリと比較して、体感で気付くくらい遅かった ● ○ 具体的には起動 →アプリのUIが表示完了するまでの時間がかかりすぎている ○ これは画面が真っ白のままユーザーを待たせるので、しっかり目立つ 同僚の調査の結果、redux-observable が依存する rxjs の特定操作が Hermesの不得意な操作をピンポイントで踏むことが分かった ○ patchを当てて対応。いまもこのパッチを当てた状態である。
計測結果 メモリ使用量 アプリ起動時間 (TTFD) (開発中に計測した perfetto-trace の mem.res.anon の値) (開発中に計測した実機での値) Hermes OFF: 243,249,152 Pixel 7: Hermes ON: 129,454,080 2s160ms から 1s488ms に改善 メモリ使用量がだいぶ削減された 🎉 ・・・ただし、LOW MEMORY KILLの頻度自体はあ まり変化がなかった Galaxy A22: 3s739ms から 2s337ms に改善 アプリ起動時間もだいぶ速くなった 🎉
リリース後のユーザーデバイスのログを見てみると... (milliseconds) TTFD 50p TTFD 90p TTFD 95p TTID 90p Hermes導入前 3665 ver. 10174 12694 4000 - 4125 Hermes導入 2306 ver. 6608 8499 3750 - 3875 -3566 (約35%) -4195 (約34%) -250 Δ -1359 (約40%改善) ※ TTFD… Time-To-Full-Display / TTID… Time-To-Initial-Display なお、同僚が調査してくれました(公平性を保つための情報開示)
② patch-packageの積極活用 ● ds300/patch-package ● RN開発ではライブラリに手を入れて自分たちの 都合の良いように改変して利用する場面が多々出てきます ○ ● それは `react-native` 本体に対して行われることもあります。 「手を入れる」方法はいくつかあります ○ forkしてGitプロジェクトとしてメンテナンスする ■ ○ ● その場合、package.jsonのバージョン指定は、そのリポジトリの URLになる つど `./node_modules` フォルダにあるファイルに patch を適用する patch-package は、patch作成・適用をかなりラクに実現できるツール
Gitリポジトリをforkすることのしんどい点 ● ● 何が変更されたのか、Gitのログなどを見ないと分からない ○ 単に targetSdk を上げただけなのか独自実装があるのかが重要 ○ 別のライブラリに入れ替えるかどうかの判断基準 修正するときのステップが多くなる ○ リポジトリを開く →編集する→コミットする→ 本体プロジェクトの package.jsonのhashを置き換える ○ ● ビルド処理が必要なプロジェクトだともう一手間かかる `@fooproject/foo-bar` みたいに org idをつけて一般公開する方法もあるが ○ プライベートで使用するのとパブリックに公開するのでは 5倍くらい負担が違う (個人差あり)
patchで管理するとうれしい点 ● 数行の変更で済む修正はらくらく対応できる ● パッケージのバージョン指定はそのまま本家のものが使える ● 修正したいときも別のIDEやエディタを立ち上げなくて良い ● リポジトリのメンテとか不要 まぁそんなわけで、出来るものはpatchに置き換えていっています
patch vs fork ● ● patchとforkの使い分けは、修正箇所がどのくらいあるかで使い分ける ○ パッチサイズが大きいなら forkのほうが良かったかもな~ってなる ○ forkしたけど差分が数行しかないなら、 patchに移行するとラクになる 結局ライブラリはアップデートされて改善されることがあるので、 その改善を素早く適用できる状態になっていればどっちでも良い ● 総合して考えるとpatchのほうがforkよりも優位性があるので、 patchのカバーできる分量内だったらpatch使いましょう
③ Lintルールの厳格化や独自ルールの整備 ● 我々の開発は「機能開発をするチーム」と 「安定性を維持するチーム」に分担して作業している ○ 僕は「安定性」のチームに属しています ● RNのアップデート等で全体的なコード品質に向き合うときがある ● そのときに結構 "スゴい" 実装が出てきてビックリすることがある ○ 国内外との競争のために速度優先をしているチームの事情はとても理解出来る ○ いっぽうで "スゴい" 実装によるパフォーマンス劣化は ユーザー体験に影響を及ぼすので、そこを我々のチームでよくしていく ● 実装のベストプラクティスは公式資料やLintルールで強制(矯正)できる
明示的に強制したルール ● ● ● react/no-unstable-nested-components ○ FC内にFCを書くことを指摘する ○ Prop drillingをサボるのに便利なのだがリファクタがマジで大変になるので厳しくした @typescript-eslint/no-explicit-any ○ 明示的に書かれた anyを指摘する ○ TS導入直後の実装でまだまだ anyが残っているが、だいぶ減らすことができてきた import/order ○ importの順番を定義したルールに沿って整列されてないと指摘。 Auto-fixableである ○ なにもしてないのに Gitのdiffが出る・・・ということがほぼなくなった気がする
ローカルルール ● eslint-plugin-local-rules を導入するとリポジトリ完結のルールが作りやすい ● `no-native-module-import` と `no-use-(selector|dispatch)-directly` を作った。 ○ `no-native-module-import` ■ 特定のファイルを除くプロジェクト全体でNativeModuleクラスを触れない ように制約 ○ `no-use-(selector|dispatch)-directly` ■ 直接useSelector / useDispatch を使っていると指摘する ■ ある時点のreduxの破壊的変更により、 useSelector から useAppSelector を使う必要が出てきたため。
今後の挑戦とか パラレルの野望を達成するために技術的な進化をし続けます。
① rtk-query を用いて非同期処理のデータ管理 ● "app state" 管理で redux と redux-observable を使っているが・・・ ○ 学習コストが高い( RxJSを知る必要がある) ○ 実現したいことに対するコストが高い(割と記述量が重い) ○ Hermesで潜在的に動作が重くなる ○ "ephemeral state" 管理にも使っちゃってる ■ ● ただ、stateのスコープが違うだけで処理の書き方が変わるのもイケてない そのあたりを改善するために、Redux ToolkitおよびRTK Queryへの置き換えを ちょっとずつ進めています ○ 俺の仕事ではなく同僚の仕事(公平性を保つための情報開示)
② RN0.75対応や、NewArchなど「その先」への対応 ● RN0.75は現在対応中です ○ RN0.76でNewArchがデフォルトONになるので、 それを見越して NewArchを有効化できないか企んでいます ● アプリの性質上、Native実装との通信が激しいため、 NewArchitecture対応がうまくいくと性能向上が期待できます
終わりに(謝辞) パラレル株式会社に興味がわいた方は 右のQRコードから 「パラレル採用情報」 をご確認ください 正社員でも、副業でも大歓迎! 情報公開を快諾してくださった パラレル株式会社のチームメンバーに感謝します! あとTechouseに興味持った人は直接僕にお声がけください