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
自体を新しいオブジェクトで置き換える必要があります。
-
コードで見る挙動の違い
簡単なカウンターの例でref
とshallowRef
の挙動の違いを見てみましょう。
<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
が役立つのでしょうか?主なユースケースはパフォーマンスの最適化です。
-
大規模で不変なデータ構造を扱う場合
ref
はオブジェクトのすべてのプロパティを再帰的に監視するため、データが非常に大きい(数千のプロパティを持つなど)場合、パフォーマンスのオーバーヘッドが大きくなる可能性があります。shallowRef
を使えば、この監視コストを回避できます。データ全体を不変なものとして扱い、変更が必要な場合は常に新しいオブジェクトで置き換える、という設計と相性が良いです。 -
外部ライブラリのインスタンスを保持する場合
地図ライブラリ(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
を呼ぶ必要があるなら、そもそもref
やreactive
を使う方が設計として適切かもしれません。
まとめ
特徴 | ref |
shallowRef |
---|---|---|
リアクティビティ | ディープ(深い) | シャロー(浅い) |
追跡対象 |
.value の再代入と、その内部の全プロパティの変更 |
.value の再代入のみ
|
パフォーマンス | 標準的 | 高パフォーマンス(監視コストが低い) |
主な用途 | 一般的な状態管理 | 大規模データ、外部ライブラリのインスタンス管理などのパフォーマンス最適化 |
shallowRef
は、Vueのリアクティビティシステムの仕組みを理解し、パフォーマンスが重要になる特定のケースで効果を発揮する強力なツールです。日常的な開発ではref
を使うのが一般的ですが、大規模なアプリケーションを開発する際には、shallowRef
を適切に使うことでパフォーマンスを大きく改善できることを覚えておくと良いでしょう。
Discussion