🔧

React Native製アプリケーションをプロファイリングしてレンダリングを最適化する

2023/08/17に公開

はじめに

アプリの快適さは、より良いプロダクトを目指していく中で軽視することはできません。最初は軽快な動作をするアプリを作っていたつもりでも、開発が進んでくると当初より動作が遅くなってしまうことがあり、定期的な操作感の見直しが必要になるケースがあります。

今回は、React Native製フロントエンドの操作性の向上に着目し、プロファイリングを通してレンダリングの様子を確認し、改善に役立てる方法をご紹介したいと思います。

動作確認環境

本記事で紹介する方法は、以下の環境で確認しています。

開発機

  • OS: Windows 10 Home
  • RAM: 32GB
  • CPU: i5-10400F
  • node: v16.20.0

実行端末

  • OS: Android 13
  • RAM: 8GB
  • SoC: Snapdragon 8 Gen 1

対象となるアプリ

対象となるアプリは expo-dev-client を使用してビルドされたものになります。

プロファイリング手順

それでは早速、プロファイリングを行っていきたいと思います。

React Developer Tools のインストール

今回はコチラの「React Developer Tools」を使用しました。

React Developer Tools · React Native

以下コマンドでインストールを行います。

npm install -g react-devtools

執筆時点でのバージョンは 4.28.0 でした。

プロファイリングを開始する

プロファイリングを開始するにあたっては、まずReact Developer Toolsを起動し、アプリから接続する形をとります。

まずは以下のコマンドで起動し、

npx react-devtools

起動直後のReact Developer Tools

React Nativeアプリから接続を行います。接続するには、React NativeアプリのDev Menuを開き(三本指タップ)、Open React Native dev menu > Debugとタップしていきます。(iOSの場合はOpen React Native dev menu > Open React DevTools)

自動的にReact Developer Toolsの表示が以下のように「No profiling data has been recorded.」と表示されるようになれば接続完了です。

React Developer Tools接続完了

また、プロファイリングを行うにあたっては、再描画の原因を記録しておいた方が後々便利ですので、以下のように「設定 > Profiler > Record why each component rendered while profiling.」のチェックボックスにチェックを入れます。

レンダリングの原因を記録する設定

ここまで完了したら「Start profiling」ボタンからプロファイリングを開始します。

プロファイリング開始

レンダリングが気になる操作を実行する

プロファイリングが開始されると以下のような表示になります。

プロファイリング中の表示

ここから操作を行うと、画面の変化が記録され、どの処理にどれほどの時間を要したかが記録されていきます。

プロファイリング結果はコミットごとに整理して表示してくれる機能があるため、プロファイリングの実行中にレンダリングの挙動を確認したい操作が含まれてさえいればよく、開始・終了はザックリとした感覚で行って大丈夫です。

それでは、プロファイリングを行いたい操作(例:タブの切り替え、ボタンのタップ)を実施しましょう。

プロファイリングを終了する

所定の動作を行いレンダリングが終了したら、プロファイリングを終了します。

プロファイリング終了

結果の確認

プロファイリングを終了すると、以下のような図が出てきます。

プロファイリング結果

詳細な見方については以下のZenn記事が参考になりますので、コチラを参照いただければと思います。

プロファイリング結果を用いた対処

ここまでは、プロファイリングを行ってレンダリングに要する時間を確認する方法をご紹介しました。この章では、Reactにおけるレンダリングに要する時間を短くする対処方法として今回は以下の2つを試してみたいと思います。

  • ①そもそものレンダリング発生回数を少なくする
  • ②レンダリングが発生したときに関数が実行される範囲を小さくする

対処①:レンダリングの発生回数を少なくする

レンダリングを発生させてしまう原因としてありがちな「カスタムフックの戻り値変化」「propsの変化」を抑制しようというものです。

ありがちなのが、カスタムフックの戻り値としてオブジェクトの参照を渡しているケースで、毎回新しいオブジェクトを返してしまっているケース。これについては useState でカスタムフック内部でステートとして持つか、 useMemo でオブジェクトをキープするようにしましょう。

useCallback対応前 useCallback対応後
useCallback対応前 useCallback対応後

今回のケースでは、とあるコンポーネントに渡していた関数が useCallback を使用していなかったがために再レンダリングが走ってしまっていました。それを修正することで50msほどレンダリング時間を短縮することができました。

対処②:レンダリングが発生したときに関数が実行される範囲を小さくする

これは「memo化」というものです。コンポーネントを React.memo 関数でくくってあげることで、「propsが変化しない限り同じ結果を返す」ようにすることができます。

memo化対応前 memo化対応後
memo化対応前 memo化対応後

今回のケースでは30msほどレンダリング時間を短縮することができました。プロファイリング結果の表示上でも変化があり、対応前は緑色で表示されていた部分が灰色に表示されるようになりました。

プロファイリングにまつわるTips

ここでは、プロファイリングを行っていて得たTipsについて書きたいと思います。

Tips1: memo化するコンポーネントには名前を付ける

実際にプロファイリングにかけてみて気づいたのですが、 React.memo を使用する際は匿名関数の状態で引数に入れるのではなく、名前を付けてから入れるのがベターだと思いました。

const AnyComponent = React.memo(() => {
    // rendering
});

とするのではなく、以下のような形式です。

const AnyComponent = () => {
    // rendering
};

const MemoizedAnyComponent = React.memo(AnyComponent);

こうすることで、プロファイリングやデバッグでコンポーネント名を表示させることができるようになります。

プロファイラにコンポーネント名を表示させる

Tips2: プロファイリング結果から再レンダリングの原因となったフックを特定する

プロファイリング結果を見ていると、レンダリングの要因(Why did this render?)として

Hook XX changed

となっていることがあります。読んだそのままフックの変更が再レンダリングを引き起こしたということになるのですが、どのフックが原因なのかこれでは特定することができません。

Why did this render?にHook 25 Changedと表示されている

そういった場合には React Developer Tools のタブを Profiler から Components に切り替え、詳細を見てみるとよいです。

ComponentsタブからHookを特定する

今回のケースだと、Profilerタブから ChatCustomChannelAvatar コンポーネントが謎のフック25変更により再レンダリングされたことがわかりますが、ここからでは要因となったフックを特定することはできません。Componentsタブから ChatCustomChannelAvatar 内のフックで25番があたっているものを探すことで原因を特定することができました。

終わりに

以上、今回はReact Nativeで実装しているアプリケーションに関して、プロファイリングを用いた描画時間短縮を題材に書かせていただきました。これからレンダリングの最適化に臨む方へ、参考になれば幸いです。

Discussion