Vue Devtoolsでパフォーマンス改善ポイントを見つける

この記事は、[Vue.js Advent Calendar 2019 #1](https://qiita.com/advent-calendar/2019/vue) 8 日目の記事です。

Vue Devtools の Performance タブを使って、パフォーマンスの改善ポイントをサクッと見つける方法を紹介したいと思います。
「**サクっと**」というのがポイントです。

さらに、見つけた改善ポイントから実際にパフォーマンスを改善する例を紹介したいと思います。

# Vue Devtools とは

Vue.js を使った開発におけるデバッグなどを助けてくれるツールです。

**[vuejs/vue-devtools](https://github.com/vuejs/vue-devtools)**

[Chrome Extension](https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)としても提供されています。
機能ごとにタブが存在しており、今回はその中の Performance タブを使います。

Vue Devtools について詳しくは以下の記事が参考になると思います。

[Vue Devtools で快適なデバッグ - ROXX(旧 SCOUTER)開発者ブログ](https://techblog.roxx.co.jp/entry/2019/05/15/131747)

## Performance タブ

Vue.js で構築されたアプリケーションのパフォーマンスを簡易的に計測できる機能です。

Chrome DevTools を使った計測ほど詳細は分からないものの、敷居が低く簡単に使うことができる印象があります。
パフォーマンスの悪いコンポーネントをサクッと見つけたい場合は、この Performance タブを使うのがおすすめです。

Performance タブの使い方についても上記の記事が参考になると思います。

# Vue Devtools でパフォーマンス改善ポイントを見つける

早速、Vue Devtools の Performance タブを使ってパフォーマンス改善ポイントを見つけてみたいと思います。

## 改善対象のコンポーネント

改善対象のコンポーネントは以下のようなものになります。

- 大量の選択肢から 1 つを選択するダイアログコンポーネント
- 選択肢にマウスオーバーすると、その選択肢の説明を下の方に表示

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63950/5355c19b-c038-db25-7cbf-66dcfe4f053a.gif)

実際の動作デモもあります。
https://vue-devtools-performance-handson.netlify.com

また、コードは以下に置いてあるのでよろしければ確認してみてください。
https://github.com/shun91/vue-devtools-performance-handson/blob/master/src/components/HeavyDialog.vue

## 動作がもっさり...

上記の動画やデモでも明らかですが、このダイアログは動作がもっさりしていて重いです。[^heavy]

まず、ボタンをクリックしてからダイアログが開くまでが重くて、アニメーションももっさりしています。
さらに、マウスオーバーすると選択肢の背景色が変わるのですが、ここのアニメーションももっさりしています。

## パフォーマンスを計測してみる

では、実際に Vue Devtools の Performance タブを使ってパフォーマンスを計測してみます。
今回は、選択肢にマウスオーバーした時のパフォーマンスを計測してみます。

まずは Frames per second から確認してみます。
その様子を動画にしてみましたが、選択肢にマウスオーバーした瞬間に明らかに fps が下がったことが分かると思います。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63950/415145bb-83a3-ae26-5939-bdac6472755d.gif)

続いて、Component render を確認してみます。
こちらも、選択肢にマウスオーバーした瞬間に何やらたくさんの render 処理が走っているようです。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63950/29edb8c8-c60b-38ee-64c8-b61f36ef0b4e.gif)

## 改善ポイントを見つける

計測結果をもう少し詳しく見てみます。

Component render では、左側のコンポーネント名をクリックすると、そのコンポーネント内で走った処理 (Lifecycle Hooks) にかかった時間と回数を確認することができます。

例えば、`<VListTile>` をクリックしてみると、以下のようになります。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63950/97441a27-6aad-5927-a222-f7fda4a6fc4d.png)

なんと、選択肢にマウスオーバーしただけなのに `updateRender` が 600 回も実行されていて、トータルで 71333ms (≒ 71 秒！) もかかっていることが分かりました...
合わせて、`<VRadio>` や `<VIcon>` も同じように `updateRender` が 600 回も実行されていました。

つまり、`<VListTile>`, `<VRadio>`, `<VIcon>` のあたりにパフォーマンスを改善するためのポイントがありそうだと言えそうです。

このように、Performance タブを使えばパフォーマンスの悪いコンポーネントを簡単に見つけ出すことができます。

この記事の目的としてはここでもう果たせたのですが、せっかくなので実際にパフォーマンスを改善するところまでやってみたいと思います。

## 改善する

先程、`<VListTile>`, `<VRadio>`, `<VIcon>` に改善ポイントがありそうだということがわかりました。

[実装](https://github.com/shun91/vue-devtools-performance-handson/blob/master/src/components/HeavyDialog.vue#L15-L30)を確認してみると、これらのコンポーネントは `v-for` で繰り返し描画されており、選択肢 1 つ 1 つを構成している要素だということが分かります。

```vue
<v-list-tile
  v-for="{ value } in items"
  :key="value"
  ripple
  @click="selected = value"
  @mouseenter="updateOnmoused(value)"
  @mouseleave="updateOnmoused('')"
>
  <v-list-tile-action>
    <v-radio :value="value" />
  </v-list-tile-action>

  <v-list-tile-content>
    <v-list-tile-title>{{ value }}</v-list-tile-title>
  </v-list-tile-content>
</v-list-tile>
```

1 つの選択肢にマウスオーバーしただけで 600 回も render 処理が走るということは、つまり、「1 つの選択肢にマウスオーバーしただけで、すべての `<VListTile>`, `<VRadio>`, `<VIcon>` が再描画されている」可能性がありそうです。[^chrome-devtools]

これは明らかに無駄です。マウスオーバーした部分だけが再描画されれば十分のはずです。

### 再描画される部分をコンポーネント化する

無駄な部分まで再描画されないようにするためには、該当部分をコンポーネント化して切り出すとよいことがあります。
詳しくは以下の記事が参考になると思います。

[コンポーネントを使って描画更新のコストを削減する | seihmd tech blog](https://seihmd.com/posts/20180304-reduce-vue-rerendering-scope/)

では、`<VListTile>` の部分を `<TheListItem>` という名前のコンポーネントに切り出してみます。

```diff
diff --git a/src/components/HeavyDialog.vue b/src/components/HeavyDialog.vue
index cd89f9c..8297e4c 100644
--- a/src/components/HeavyDialog.vue
+++ b/src/components/HeavyDialog.vue
@@ -1,7 +1,7 @@
 <template>
   <v-dialog v-model="dialog" scrollable max-width="300px">
     <template #activator="{ on }">
-      <v-btn v-on="on" dark>open heavy dialog</v-btn>
+      <v-btn v-on="on">open light dialog</v-btn>
     </template>

     <v-card>
@@ -12,22 +12,14 @@
       <v-card-text class="pa-0" style="height: 300px;">
         <v-radio-group v-model="selected">
           <v-list class="pa-0">
-            <v-list-tile
-              v-for="{ value } in items"
-              :key="value"
-              ripple
-              @click="selected = value"
-              @mouseenter="updateOnmoused(value)"
+            <the-list-item
+              v-for="item in items"
+              :key="item.value"
+              :item="item"
+              @click="selected = item.value"
+              @mouseenter="updateOnmoused(item.value)"
               @mouseleave="updateOnmoused('')"
-            >
-              <v-list-tile-action>
-                <v-radio :value="value" />
-              </v-list-tile-action>
-
-              <v-list-tile-content>
-                <v-list-tile-title>{{ value }}</v-list-tile-title>
-              </v-list-tile-content>
-            </v-list-tile>
+            />
           </v-list>
         </v-radio-group>
       </v-card-text>
@@ -45,8 +37,11 @@

 <script lang="ts">
 import Vue from "vue";
+import TheListItem from "./TheListItem.vue";

 export default Vue.extend({
+  components: { TheListItem },
+
   data: () => ({
     dialog: false,
     selected: "",
```

変更後のコードは [LightDialog.vue](https://github.com/shun91/vue-devtools-performance-handson/blob/master/src/components/LightDialog.vue) として保存します。

`<TheListItem>` の実装は以下のようになります。

```vue
<template>
  <v-list-tile
    ripple
    @click="$emit('click')"
    @mouseenter="$emit('mouseenter')"
    @mouseleave="$emit('mouseleave')"
  >
    <v-list-tile-action>
      <v-radio :value="item.value" />
    </v-list-tile-action>

    <v-list-tile-content>
      <v-list-tile-title>{{ item.value }}</v-list-tile-title>
    </v-list-tile-content>
  </v-list-tile>
</template>

<script lang="ts">
import Vue, { PropType } from "vue";

export default Vue.extend({
  props: { item: { type: Object as PropType<any>, required: true } }
});
</script>
```

## 改善後のパフォーマンスを計測してみる

コードを修正できたので、再度パフォーマンスを計測してみます。

まずは Frames per second からです。
見事に 60fps を維持することができています！

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63950/a66287e4-fcc4-24c9-b66c-b4a2fff2e76a.gif)

続いて Component render です。
`<VListTile>` は 1 回どころかまったく再描画されなくなりました！

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63950/788439a5-0625-5ba6-af2f-d86aff7691c8.gif)

改善前後を見比べても確かに動作が軽快になっていることが分かります。
動画だと分かりにくい場合は実際の[デモ](https://vue-devtools-performance-handson.netlify.com)を触ってみてください。

| Before                                                                                                          | After                                                                                                           |
| --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| ![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63950/5355c19b-c038-db25-7cbf-66dcfe4f053a.gif) | ![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63950/a2595141-edab-3d04-27a6-6ca7ec4215c5.gif) |

# まとめ

この記事では、Vue Devtools の Performance タブを使って、パフォーマンスの改善ポイントを見つける方法を紹介しました。
さらに、見つけた改善ポイントに修正を加えて、実際にパフォーマンスを改善しました。

- Performance タブを使えばパフォーマンスの悪いコンポーネントを簡単に見つけ出すことができます。
- Chrome DevTools を使った計測ほど詳細は分からないものの、敷居が低く簡単に使うことができます。

また、今回使用したコードは以下のリポジトリに置いてありますのでよかったら確認してみてください。

shun91/vue-devtools-performance-handson
https://github.com/shun91/vue-devtools-performance-handson

最後までお読みいただきありがとうございました！mm

## おまけ

- 「ボタンをクリックしてからダイアログが開くまでが重い」のも、Performance タブを使って改善ポイントを見つけることができます。こちらはぜひご自身の手で試してみてください。 [^vue-virtual-scroller]
- Vue Devtools の Performance タブについての記事が意外と見つけられなかったので、この記事が少しでも誰かのお役に立てれば嬉しいです。
- この記事を書くためのデモを実装するのに、はじめは Vuetify 2.x を使っていたのですが、プロダクションビルドすると思ったようにパフォーマンスが悪くならない現象に遭遇しました。Vuetify 1.x にダウングレードしたところ、想定通りパフォーマンスが悪くなったので、Vuetify 2.x ではプロダクションビルド時に何らかのパフォーマンス最適化が行われるようになったみたいです。（すみません、詳しくは調べられていないです...）

[^heavy]: あえて重くなるような作りにしているだけではありますが...
[^chrome-devtools]: この可能性を確信に変えるには、Chrome DevTools などでさらなる検証が必要ですが、この記事の主旨からはズレるので割愛します mm
[^vue-virtual-scroller]: 仮想スクロールを使うとパフォーマンスを改善できます。Vue.js だと [vue-virtual-scroller](https://github.com/Akryum/vue-virtual-scroller) というライブラリがあります。