Skip to content

コンポジションAPIで$refsを使用する方法

Vue 3のコンポジションAPIを使用する際に、テンプレート参照(template refs)を適切に取得する方法について解説します。

問題点

Vue 2のOptions APIでは、this.$refsを使用して簡単に子コンポーネントやDOM要素への参照を取得できました:

js
mounted() {
  console.log(this.$refs.table.temp());
}

しかし、コンポジションAPIではthisが利用できないため、異なるアプローチが必要です。

基本的な解決策

単一要素の参照

コンポジションAPIでは、ref()関数を使用してテンプレート参照を作成します:

vue
<template>
  <div ref="table"/>
</template>

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

export default {
  setup() {
    const table = ref(null);

    onMounted(() => {
      console.log(table.value); // DOM要素またはコンポーネントインスタンス
    });

    return { table };
  }
}
</script>

コンポーネントのメソッド呼び出し

子コンポーネントのメソッドを呼び出す場合:

vue
<!-- 親コンポーネント -->
<template>
  <ChildComponent ref="childRef"/>
</template>

<script>
import { ref, onMounted } from 'vue';
import ChildComponent from './ChildComponent.vue';

export default {
  components: { ChildComponent },
  setup() {
    const childRef = ref(null);

    onMounted(() => {
      childRef.value.someMethod(); // 子コンポーネントのメソッドを呼び出し
    });

    return { childRef };
  }
}
</script>
vue
<!-- 子コンポーネント -->
<script>
export default {
  setup() {
    const someMethod = () => {
      console.log('子コンポーネントのメソッドが呼ばれました');
    };

    return { someMethod };
  }
}
</script>

注意

参照はコンポーネントがマウントされた後にのみアクセス可能です。onMountedライフサイクルフック内や、それ以降のタイミングで参照を使用してください。

複数要素の参照

v-forディレクティブで複数の要素を参照する場合、いくつかの方法があります。

方法1: 配列での参照(Vue 3.2.25〜3.2.31の問題あり)

vue
<template>
  <div v-for="(item, i) in items" ref="elements" :key="item.id">
    <div>ID: {{ item.id }}</div>
    <div>Name: {{ item.name }}</div>
  </div>
</template>

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

export default {
  setup() {
    const items = [
      {id: 1, name: "item name 1"},
      {id: 2, name: "item name 2"},
      {id: 3, name: "item name 3"},
    ];
    const elements = ref([]);

    onMounted(() => {
      console.log(elements.value.map(el => el.textContent));
    });

    return { elements, items };
  }
}
</script>

注意

Vue 3.2.25から3.2.31のバージョンでは、この方法で参照の配列が空になる問題があります。

方法2: 関数参照(推奨)

互換性の高い方法として、関数参照を使用します:

vue
<template>
  <div 
    v-for="(item, i) in items"
    :ref="el => elements[i] = el"
    :key="item.id"
  >
    <div>ID: {{ item.id }}</div>
    <div>Name: {{ item.name }}</div>
  </div>
</template>

TypeScriptでの型付け

TypeScriptを使用する場合、適切な型を指定します:

typescript
import { ref, onMounted } from 'vue';

const table = ref<HTMLDivElement | null>(null);
// またはコンポーネントの型を指定
// const childComponent = ref<InstanceType<typeof ChildComponent> | null>(null);

onMounted(() => {
  if (table.value) {
    // 型安全なアクセス
    table.value.addEventListener('click', () => console.log("クリックされました"));
  }
});

条件付きレンダリングの考慮

参照がv-ifなどで条件付きにレンダリングされる場合、参照がnullになる可能性があります:

vue
<template>
  <div v-if="isVisible">
    <div ref="conditionalRef"/>
  </div>
</template>

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

export default {
  setup() {
    const isVisible = ref(false);
    const conditionalRef = ref(null);

    watch(isVisible, (newValue) => {
      if (newValue && conditionalRef.value) {
        // 参照が利用可能になった
        console.log(conditionalRef.value);
      }
    });

    return { isVisible, conditionalRef };
  }
}
</script>

Vue 3.5+での最新アプローチ

Vue 3.5以降では、useTemplateRef関数が利用可能です(実験的機能):

vue
<script setup>
import { useTemplateRef } from 'vue';

const tableRef = useTemplateRef('table');
</script>

<template>
  <div ref="tableRef">コンテンツ</div>
</template>

まとめ

コンポジションAPIでのテンプレート参照の基本:

  1. ref()で参照を作成し、テンプレートで同じ名前を指定
  2. onMounted以降で参照を利用
  3. 条件付きレンダリングの場合、参照の可用性を確認
  4. TypeScriptでは適切な型を指定
  5. 複数要素の参照には関数参照が確実

これらの方法を適切に使い分けることで、コンポジションAPIでもOptions APIと同様にテンプレート参照を効果的に利用できます。