Vue3 shallowRefについて調べてみた

に公開

はい、Vue 3のshallowRefについて詳しく解説しますね。

shallowRefは、Vue 3のComposition APIで提供される関数の一つで、浅い (shallow) リアクティブな参照を作成します。これは、よく使われるrefと似ていますが、リアクティビティの追跡範囲に大きな違いがあります。

一言で言うと、shallowRef.valueプロパティの再代入のみを検知し、オブジェクト内部のプロパティ変更は追跡しません。


refとの主な違い

shallowRefを理解する上で最も重要なのが、refとの違いです。

  • ref: ディープ(深い)リアクティビティ

    • .valueにオブジェクトや配列が代入されると、Vueは内部的にそのオブジェクト全体をreactiveでラップします。
    • これにより、ネストされたオブジェクトのプロパティ変更も検知し、ビューが自動的に更新されます。
    • : state.value.user.name = 'Taro' のような変更も検知します。
  • shallowRef: シャロー(浅い)リアクティビティ

    • .valueの変更(再代入)だけを追跡します。
    • .valueに代入されたオブジェクトの内部プロパティが変更されても、Vueはそれを検知せず、ビューは更新されません。
    • : state.value.user.name = 'Taro' のような変更を検知しません。ビューを更新するにはstate.value自体を新しいオブジェクトで置き換える必要があります。

コードで見る挙動の違い

簡単なカウンターの例でrefshallowRefの挙動の違いを見てみましょう。

<template>
  <div>
    <h3>refの例</h3>
    <p>カウント: {{ refState.count }}</p>
    <button @click="incrementRef">カウントアップ(内部を変更)</button>
  </div>
  <hr>
  <div>
    <h3>shallowRefの例</h3>
    <p>カウント: {{ shallowState.count }}</p>
    <button @click="incrementShallow">カウントアップ(内部を変更)</button>
    <button @click="replaceShallow">カウントアップ(.valueを置換)</button>
  </div>
</template>

<script setup>
import { ref, shallowRef } from 'vue';

// refの例
const refState = ref({ count: 0 });

const incrementRef = () => {
  // .valueの内部プロパティを変更
  refState.value.count++;
  // -> ビューは "1" に更新される
};

// shallowRefの例
const shallowState = shallowRef({ count: 0 });

const incrementShallow = () => {
  // .valueの内部プロパティを変更
  shallowState.value.count++;
  console.log(shallowState.value.count); // コンソールには "1" と表示される
  // -> しかし、ビューは更新されない!
};

const replaceShallow = () => {
  // .value自体を新しいオブジェクトで置き換える
  shallowState.value = { count: shallowState.value.count + 1 };
  // -> ビューは更新される
};
</script>

この例から分かるように、shallowRef.valueを丸ごと新しいオブジェクトで置き換えた場合にのみ、変更を検知してビューを更新します。


shallowRefの使いどころ

では、どのような場面でshallowRefが役立つのでしょうか?主なユースケースはパフォーマンスの最適化です。

  1. 大規模で不変なデータ構造を扱う場合
    refはオブジェクトのすべてのプロパティを再帰的に監視するため、データが非常に大きい(数千のプロパティを持つなど)場合、パフォーマンスのオーバーヘッドが大きくなる可能性があります。shallowRefを使えば、この監視コストを回避できます。データ全体を不変なものとして扱い、変更が必要な場合は常に新しいオブジェクトで置き換える、という設計と相性が良いです。

  2. 外部ライブラリのインスタンスを保持する場合
    地図ライブラリ(Leafletなど)やグラフ描画ライブラリ(D3.jsなど)のインスタンスをコンポーネントの状態で管理したい場合があります。これらのインスタンスはVueのリアクティビティシステムとは無関係に巨大で複雑な内部状態を持っています。

    これをrefでラップすると、Vueが不要な内部プロパティまで監視しようとしてパフォーマンスが低下する可能性があります。shallowRefを使い、インスタンスそのものを保持することで、このオーバーヘッドを防げます。

    import { shallowRef, onMounted } from 'vue';
    import SomeLibrary from 'some-library';
    
    const libraryInstance = shallowRef(null);
    
    onMounted(() => {
      // ライブラリのインスタンスを.valueに代入。
      // これ以降、インスタンス内部の状態が変わってもVueは関知しない。
      libraryInstance.value = new SomeLibrary(document.getElementById('element'));
    });
    

注意点: triggerRefによる手動更新

shallowRefのオブジェクト内部を変更した後で、何らかの理由で強制的にビューを更新したい場合があるかもしれません。その場合はtriggerRef関数を使います。

triggerRefは、shallowRefに関連付けられたエフェクト(描画更新など)を手動でトリガーします。

import { shallowRef, triggerRef } from 'vue';

const shallowState = shallowRef({ count: 0 });

const forceUpdate = () => {
  // 内部の値を変更
  shallowState.value.count++;
  
  // Vueに変更を通知し、再描画を強制する
  triggerRef(shallowState);
};

ただし、これは最終手段と考えるべきです。頻繁にtriggerRefを呼ぶ必要があるなら、そもそもrefreactiveを使う方が設計として適切かもしれません。


まとめ

特徴 ref shallowRef
リアクティビティ ディープ(深い) シャロー(浅い)
追跡対象 .valueの再代入と、その内部の全プロパティの変更 .valueの再代入のみ
パフォーマンス 標準的 高パフォーマンス(監視コストが低い)
主な用途 一般的な状態管理 大規模データ、外部ライブラリのインスタンス管理などのパフォーマンス最適化

shallowRefは、Vueのリアクティビティシステムの仕組みを理解し、パフォーマンスが重要になる特定のケースで効果を発揮する強力なツールです。日常的な開発ではrefを使うのが一般的ですが、大規模なアプリケーションを開発する際には、shallowRefを適切に使うことでパフォーマンスを大きく改善できることを覚えておくと良いでしょう。

GitHubで編集を提案

Discussion