98
75

More than 5 years have passed since last update.

vuejsで作ったSPAの速度改善方法

Last updated at Posted at 2018-07-11

こんにちわ。
formrunというフォームを簡単に作れてお問い合わせも出来ちゃうサービスを作ってます。
最近問い合わせ管理の機能をSPAでフルリプレイスしました。
当初は遅くてSPAにした意義がまるでなかったのですが、以下に挙げたチューニングを行う事でだいぶパフォーマンスが改善したのでアップします。

問題点

フォームの切り替え、カードの切り替えがAPIの実行などは行なっていないにも関わらず遅い。

改善結果

カード切り替え

before 700ms
after 150ms

フォーム切り替え

before 1200ms
after 430ms

サーバサイド

API

REST API N+1問題

当初はREST APIに従って一つのエンドポイントでは一つのリソースを取得するようにしていました。
ただこの作りだとリクエストのN+1が起きてしまうため極力一つのAPIのエンドポイントでは大きくデータを取得するように変更しました。

スクリーンショット_2018-07-03_1_17_07.png

formrunではjbuilderを使用してAPIのレスポンスを組み立てていますが、GraphQLを使用してN+1を回避するのもいいと思います。
というか作り直したい。

フロントサイド

フロントボトルネック調査

vue performance devtool

chrome pluginのvue-performance-toolを使用してボトルネックのコンポーネントを調査する方法です。
vue_performance.gif

まずはrecordingをクリックします。
調査を行いたい操作を行いrecodingをストップします。
すると各コンポーネントにかかっている時間がms単位で表示されてるので、Total Timeが大きい箇所から調査していきます。
また各値の意味は下の通りです。

  • Init beforeCreated と createdにかかった時間
  • Render githubにjsのインスタンスを作成するためにかかった時間とあるので、仮想DOMの生成にかかった時間?
  • Patch DOMをレンダリングするためにかかった時間
  • Total Time 1つでもNaNmsと出ている箇所があると合計時間がNaNmsになってしまうので各値も確認した方が良いかも

上記のサンプルではCardNewは内包しているDOMの数もそれほど多いわけではないのにRenderに時間がかかっているため優先的に調査していきます。
またカード切り替えとはほぼ関係の無いコンポーネントが再描画されているので、カード切り替えを遅くしている原因の一つだとわかりました。

chrome performance

chromeの開発者ツールにperformance計測のためのツールを使ってボトルネックを調査する方法です。
chrome_performance.gif

まずは開発者ツールのperformanceタブを開いて録画ボタンをクリックします。
その後パフォーマンスを確認したい操作を行い録画をストップします。
するとページ下部に以下のような図が出てきます。

スクリーンショット_2018-07-03_3_23_32.png

横の長さが処理にかかっている時間になっており、縦がスタックトレースになっています。
同じ色の箇所は同じコードの処理になります。
クリックするとページ下部のSummaryのFunctionに該当のソースコードへのリンクが付きます。
このリンクをクリックするよ下記のように表示されます。

スクリーンショット 2018-07-03 3.55.31.png

これだけだとわかりにくいかもしれませんが、vueファイルのrenderが呼ばれているので、コンポーネントがrenderされている事がわかります。
上記のサンプルではCardNew.vueコンポーネントが何度も呼ばれています。
ぱっと見で確認するべき項目は同じ色が何度も表示されていて、時間がかかっている箇所を優先的に調査していきます。

フロントサイド速度改善

大量コンポーネントのスクロール

大量のコンポーネントを表示してスクロールを行うとスムーズにスクロールが出来ませんでした。
同様の課題を抱えてるvueユーザがvuejsのissueを上げていました。
結論としてはvue-virtual-scrollerを使えという回答でした。

ただvue-virtual-scrollerスクロール対象のコンポーネントの高さが不定な場合に変数で指定出来る仕組みがありますが、カードの中に表示させるラベルが複数指定可能なため、都度高さの計算が必要だったため、導入を諦めました。

state更新による大量のコンポーネントの再描画

formrunではAPIで取得したデータとデータ同士のリレーションをvuex-ormを使用して管理しています。
vuex-ormでは関連するデータの取得にwith withAllを指定してリレーション先のデータを取得するようにしています。
開発当初は必要なリレーション一つ一つを指定していくのが面倒だったため、とりあえずwithAllですべてのリレーション先を取得するようにしていました。
これが意図しないコンポーネントの再描画の原因です。
vuex-ormのサンプルを使用して再現してみます。

サンプルはよくあるTODOです。
スクリーンショット_2018-07-03_20_12_04.png

モデル構成は以下です。

User -> Todo -> Comment
 |                 |
 +-----------------+

Todo一覧ではwithAllを呼び出してTodoのUserとCommentも取得しています。
スクリーンショット_2018-07-03_20_12_04.png

loadCommentボタンでコメント情報を更新します。
スクリーンショット_2018-07-03_20_12_04.png

chromeのperformanceで計測した結果が以下です。
スクリーンショット_2018-07-03_20_28_06.png

loadCommentしたあとにTodoList.vueが再描画されてます。
TodoList.vueでは一切コメントを表示していませんが、withAllでCommentオブジェクトも取得してしまっているため、Commentを更新したときにTodoList.vueが再描画される原因となっています。
これを修正するためにはcomputedに定義したメソッドで必要なStateのみ取得するように修正します。
修正した結果は以下です。

スクリーンショット_2018-07-03_20_41_37.png

loadCommentしたあとにupdateComponentが無くなっています。
ちなみにこれはvuex-ormを使わなかった場合でも起きるのでcomputedやgetters内では必要なstateのみを扱うように修正しました。

強制レイアウトの解消

参考資料

ブラウザがDOMツリーを構築したあとに、レイアウト計算(reflow)と描画があります。
jsの操作によってreflowの回数が増えてしまい描画に時間がかかってしまうためperformanceタブでreflowが存在していたら解消していきます。

スクリーンショット_2018-07-03_23_11_30.png

SimpleAjaxUploder.jsはファイルアップロード用のライブラリなので、他ライブラリへ乗り換えを検討しています。
このreflowを引き起こす要因は下記プロパティへ参照しただけで起こります。

  • offsetTop offsetLeft offsetWidth offsetHeight
  • scrollTop scrollLeft scrollWidth scrollHeight
  • clientTop clientLeft clientWidth clientHeight

主にポジションの取得のためのプロパティのため、レイアウト計算を行わないとならないためreflowが発生します。

今後やりたい速度改善方法

  1. マスタに近いデータの永続化
  2. vue-routerの遅延コンポーネント
98
75
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
98
75