0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Vue 3のshallowRefを学ぶ|Dynamic Componentを切り替えてみる

0
Posted at

今日はshallowRef()を中心に、Vue 3でコンポーネントを切り替える方法を整理しました。
ref()との違いが最初はかなり曖昧だったのですが、Dynamic Componentの例にすると「どこまでVueに変更を見てもらうか」が少し分かりやすくなりました。

フロント側のスキルを増やして、一覧・詳細・設定のような画面を自分でも組み立てられるようになりたいです。

まだ途中段階なので、勘違いなどあれば教えていただけるとうれしいです。

今日やったこと

shallowRef()で現在表示するコンポーネントを持ち、ボタンで表示を切り替える形を確認しました。

今回用意するのは、次の3つです。

  • CompA.vue
  • CompB.vue
  • CompC.vue

たとえば、CompA.vueは次のように小さく作れます。

<template>
  <!-- compAを選択したときに表示する内容 -->
  <section>
    <h2>compA</h2>
    <p>Aの画面を表示しています。</p>
  </section>
</template>

CompB.vueCompC.vueにも、それぞれ別の表示内容を書きます。
親コンポーネントでは、各コンポーネントの中身ではなく「今どれを表示するか」だけを管理するイメージです。

キャラクター 「compA・B・Cのどれを出すかだけ覚えておけばいいなら、役割を分けて考えられそうだね。」

Vueでは、<component>:is属性を使うと、変数に入っているコンポーネントを表示できます。これがDynamic Componentです。Vue.js公式ドキュメント

<script setup>
import { shallowRef } from 'vue'

// 切り替えたいコンポーネントを読み込む
import CompA from './components/CompA.vue'
import CompB from './components/CompB.vue'
import CompC from './components/CompC.vue'

// 最初に表示するコンポーネントを保持する
// コンポーネントの中身ではなく、表示対象の入れ替えを追跡したい
const currentComponent = shallowRef(CompA)

// 押したボタンに対応するコンポーネントへ表示対象を切り替える
const changeComponent = (component) => {
  currentComponent.value = component
}
</script>

<template>
  <main>
    <h1>コンポーネント切り替え</h1>

    <div>
      <!-- クリックしたコンポーネントを表示対象にする -->
      <button @click="changeComponent(CompA)">compA</button>
      <button @click="changeComponent(CompB)">compB</button>
      <button @click="changeComponent(CompC)">compC</button>
    </div>

    <!-- currentComponentに入っているコンポーネントを表示する -->
    <component :is="currentComponent" />
  </main>
</template>

このコードでは、compAボタンを押すとCompAcompBを押すとCompBが表示されます。ボタン部分は変わらず、<component :is="currentComponent" />の場所だけが切り替わる形です。

タブのようなUIはもちろん、画像解析結果の「一覧」「詳細」「条件設定」を同じ画面内で切り替える場面にも使えそうです。

つまずいたところ

ref()shallowRef()は、オブジェクトの中身まで追跡するかどうかが違うと理解しました。

まず、shallowは英語で「浅い」という意味です。
shallowRef()は、オブジェクトを入れたときに、その一番外側だけをリアクティブに扱います。

通常のref()でオブジェクトを持つ場合を考えます。

<script setup>
import { ref } from 'vue'

// refはオブジェクトの中にある値もリアクティブに扱う
const user = ref({
  name: 'Yuki',
  settings: {
    theme: 'dark'
  }
})

const changeTheme = () => {
  // themeだけの変更でも、Vueは変更を検知できる
  user.value.settings.theme = 'light'
}
</script>

この場合、user.value.settings.themeのようにオブジェクトの内側にある値を変更しても、Vueは変更に気付きます。フォームの入力値や、一覧のカードにある状態など、細かい変更を画面へ反映したいデータにはref()が使いやすそうです。

一方で、今回のcurrentComponentに入るCompACompBは、コンポーネント定義を持つオブジェクトです。Dynamic Componentで必要なのは、コンポーネント定義の中身を編集することではなく、「表示対象をCompAからCompBへ入れ替えること」です。

キャラクター 「中身の細かい変更ではなく、今どの画面を選んだかだけを見たい。そこがshallowRef()の出番なんだね。」

ref()へコンポーネントを渡すこと自体はできますが、Vueはコンポーネントもオブジェクトとして扱うため、その内部までリアクティブにしようとします。開発中に「コンポーネントがリアクティブなオブジェクトになっている」という趣旨の警告が出ることがあるのは、このためです。

これは必ず動かなくなるエラーではなく、「コンポーネントの中身まで監視すると、不要な処理になるかもしれない」という注意だと今は理解しています。shallowRef()は、値の内部を深くリアクティブにせず、.valueの入れ替えだけを追跡できます。Vue.js公式ドキュメント vuejs

<script setup>
import { shallowRef } from 'vue'
import CompA from './components/CompA.vue'
import CompB from './components/CompB.vue'

// 最初はCompAを表示対象として持つ
const currentComponent = shallowRef(CompA)

const showCompB = () => {
  // .valueそのものをCompBへ入れ替えるため、Vueは変更を検知できる
  currentComponent.value = CompB
}
</script>

ゲームの装備画面にたとえると、shallowRef()は「今どの装備を選んでいるか」を覚える役割に近そうです。剣から盾へ選び直したことは画面へ反映したいですが、選択中の装備の内部データまでVueが細かく見張る必要はない、というイメージです。

今の理解(メモ)

今の自分の頭の中では、shallowRef()は「中身ではなく、入れ物ごと交換したいとき」に向いていそうです。

  • ref()は数値、文字列、配列、オブジェクトなどをリアクティブに持てる
  • ref()へオブジェクトを渡すと、オブジェクト内部の値の変更も追跡できる
  • shallowRef()も、値は.valueで扱う
  • shallowRef()は、.valueに入っているオブジェクトの内部を深くリアクティブにしない
  • currentComponent.value = CompBのように、別のコンポーネントへ入れ替える操作は検知される
  • currentComponent.value.someValue = '...'のように内部だけを書き換えても、通常は画面更新の対象として追跡されない

今回の切り替えの流れは、次のように見えています。

shallowRef()は、すべてのオブジェクトに使うものではなさそうです。

たとえば、検索フォームの入力値や、画像解析結果一覧の1件ごとの状態は、値の中身が変わったら画面へ反映したいのでref()が合いそうです。反対に、コンポーネント、地図やグラフなどの外部ライブラリのインスタンス、大きなデータを丸ごと交換するケースでは、shallowRef()を検討できそうです。

次にやりたいこと

まずは小さなタブ画面を作り、shallowRef()の動きを自分の手で確認したいです。

  • CompACompBCompCを切り替えるタブ風の画面を作る
    ボタン操作と:is属性がどうつながるかを確認したいです
  • ref()版とshallowRef()版を並べる
    DevToolsやコンソールに出る警告、表示の切り替わり方を見比べたいです
  • 各コンポーネントに入力欄を置く
    切り替えたあとに入力内容が残るかを確かめたいです
  • <KeepAlive>を組み合わせる
    非表示になったコンポーネントの状態を保持できるか試したいです
  • 一覧・詳細・設定の仮画面に置き換える
    画像解析結果を扱う画面で、どの単位でコンポーネントを分けると見やすいか考えたいです
キャラクター 「まずは表示を切り替えるだけの小さな画面で、ref()との違いを見比べるのがよさそう。」

今日の整理

shallowRef()は、値の中身よりも「何を選んでいるか」「値が丸ごと変わったか」を扱いたいときに使えそうです。

用語 今の理解での役割 利用イメージ 次に確認したいこと
ref() 値とオブジェクト内部の変更を追跡する フォーム、一覧データ、表示状態 入れ子の値を変えたときの更新
shallowRef() .valueの入れ替えを主に追跡する 表示中のコンポーネント 内部変更と再代入の違い
<component> 動的にコンポーネントを表示する タブの表示エリア 通常のコンポーネントとの使い分け
:is 表示対象のコンポーネントを指定する CompAからCompBへの切り替え 文字列で指定する場合との違い
Dynamic Component 選択に応じて画面の部品を入れ替える 一覧・詳細・設定の切り替え <KeepAlive>との組み合わせ

おわりに

shallowRef()は、「浅いリアクティブ」という言葉だけでは少しつかみにくかったです。ただ、Dynamic Componentの切り替えに当てはめると、コンポーネントの中身ではなく、現在選択しているコンポーネントを入れ替えたいだけだと考えられて、少し整理できました。

まだなんとなく動く段階ではありますが、フロント側の画面構成が少し見えてきた気がします。今後は、画像解析結果の一覧・検索・詳細表示を見やすく扱うために、コンポーネントの分け方も試していきたいです。

学習中の内容なので、勘違いや理解不足もあると思います。もし気になる点があれば教えてもらえるとうれしいです。

 注意事項
 本ブログに掲載している内容は、私個人の見解であり、
 所属する組織の立場や戦略、意見を代表するものではありません。
 あくまでエンジニアとしての経験や考えを発信していますのでご了承ください。

0
0
0

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?