【Vue】useStateと一緒にshallowRefを使うとパフォーマンスが上がるって本当?

2024/02/04に公開

はじめに

useState に関して気になることがあったので検証してみた。
ドキュメント的にはこんな内容

要するに配列やオブジェクトの深い部分を変更した際にリアクティブにする必要がない場合、useState と shallowRef を組み合わせることで、パフォーマンスを向上させることができるって書いてある。

※公式ドキュメント
https://nuxt.com/docs/api/composables/use-state

shallowRef を使わない挙動確認

実際の挙動を見るために下記サンプルコードで確認してみた。
以下は shallowRef を使わないサンプル

composables/state.ts
export const useShallowState = () =>
  useState("my-shallow-state", () => shallowRef({ deep: "not reactive" }));

export const useNotShallowState = () =>
  useState("my-state", () => {
    return {
      deep: "not reactive",
    };
  });
index.vue
<template>
  <div>
    <p>{{ notShallowState }}</p>
    <button @click="updateState">Change Deep Property</button>
  </div>
</template>

<script setup>
import { useNotShallowState } from "../composables/state";

const notShallowState = useNotShallowState("my-state");

// 状態を更新する関数
function updateState() {
  // ①オブジェクトの値を変更
  notShallowState.value.deep = "notShallowStateのdeepを変更";
  console.log("notShallowState.value.deep", notShallowState.value.deep);
  // ②オブジェクト自体を変更
  // notShallowState.value = { deep: "オブジェクト自体を変更" };
  // console.log("notShallowState.value.deep", notShallowState.value.deep);
}
</script>

① はオブジェクトの値を書き換え、② はオブジェクト自体を書き換えている

① オブジェクトの値を書き換えた結果

値も画面も更新されているのがわかる。

② オブジェクト自体を書き換えた結果

同様に値も画面も変更されているのがわかる。

shallowRef を使った挙動確認

以下は shallowRef を使ったサンプル

index.vue
<template>
  <div>
    <p>{{ shallowState }}</p>
    <button @click="updateState">Change Deep Property</button>
  </div>
</template>

<script setup>
import { useShallowState } from "../composables/state";

// shallowRefを使用してリアクティブな状態を作成
const shallowState = useShallowState("my-shallow-state");

// 状態を更新する関数
function updateState() {
  // ① この変更はリアクティブではないため、ビューは更新されない
  shallowState.value.deep = "shallowStateのdeepを変更";
  console.log("shallowState.value.deep", shallowState.value.deep);
  // ② state自体を置き換えるとビューが更新される
  // shallowState.value = { deep: "オブジェクト自体を変更" };
  // console.log("shallowState.value.deep", shallowState.value.deep);
}
</script>

① オブジェクトの値を書き換えた結果

値は変更されているが、画面は更新されていないのがわかる。

② オブジェクト自体を書き換えた結果

値も画面も変更されているのがわかる。

パフォーマンスの比較

2 つの挙動がわかったところで、パフォーマンスの検証をしたい。
やり方はとりあえずバカでかい配列を用意して、両方の処理の 開始時間 と 終了時間 の差分を出して比較する。

shallowRef を使わない ver

以下サンプルコード

composables/state.ts
export const useShallowState = () =>
  useState("my-shallow-state", () =>
    shallowRef({
      nested: Array.from({ length: 10000 }, (_, i) => ({ id: i })),
    })
  );

export const useNotShallowState = () =>
  useState("my-state", () => {
    return {
      nested: Array.from({ length: 10000 }, (_, i) => ({ id: i })),
    };
  });
index.vue
<template>
  <div>
    <p>{{ notShallowState }}</p>
    <button @click="updateState">Change Deep Property</button>
  </div>
</template>

<script setup>
import { useNotShallowState } from "../composables/state";

const notShallowState = useNotShallowState("my-state");

// 状態を更新する関数
function updateState() {
  const start = performance.now();
  notShallowState.value.deep = "notShallowStateのdeepを変更";
  const end = performance.now();
  console.log("結果!", end - start);
}
</script>

実行結果は下記の通り

shallowRef を使う ver

index.vue
<template>
  <div>
    <p>{{ shallowState }}</p>
    <button @click="updateState">Change Deep Property</button>
  </div>
</template>

<script setup>
import { useShallowState } from "../composables/state";

// shallowRefを使用してリアクティブな状態を作成
const shallowState = useShallowState("my-shallow-state");

// 状態を更新する関数
function updateState() {
  const start = performance.now();
  shallowState.value.deep = "shallowStateのdeepを変更";
  const end = performance.now();
  console.log("結果!", end - start);
}
</script>

実行結果は下記の通り

まとめ

検証結果として、shallowRef を使用する方が処理速度が早くなる。
よって公式ドキュメントに記載がある通り、DOM を更新する必要がない場合 useState と shallowRef を組み合わせることで、パフォーマンスが向上するとわかった。
ネストされたデータ構造が大きく、その全てをリアクティブに保持する必要がない場合には積極的に使っていきたい。

Discussion