[Vue入門] Template Refs

テンプレート参照

Vue の宣言型レンダリングモデルは、直接的な DOM 操作のほとんどを抽象化してくれます。それでも、基盤の DOM 要素に直接アクセスすることが必要になるケースがまだ存在するかもしれません。次に示す ref という特殊な属性を用いると、それを実現することができます:

<input ref="input">

ref は、v-for の章で説明した key 属性に似た、特殊な属性です。これを使用すると、特定の DOM 要素や子コンポーネントのインスタンスがマウントされた後に、そのインスタンスへの直接の参照を取得することができます。例えば、コンポーネントがマウントされた時にプログラムを使って入力欄にフォーカスを当てたり、ある要素に使用するサードパーティのライブラリーを初期化したりしたい時に便利です。

参照へのアクセス

結果として得られる参照は、以下のように this.$refs で公開されます:

<script>
export default {
  mounted() {
    this.$refs.input.focus()
  }
}
</script>

<template>
  <input ref="input" />
</template>

参照にアクセスできるのは、コンポーネントがマウントされた後に限られることに注意してください。テンプレートの式で $refs.input にアクセスしようとしても、初回のレンダリングでは null になっています。なぜなら、初回のレンダリングが終わった後でないと要素が存在しないためです!

v-for の中の参照

v3.2.25 以降が必要です。

v-for の中で ref を使用すると、結果として得られる参照の値は、対応する要素を格納する配列になります:

<script>
export default {
  data() {
    return {
      list: [
        /* ... */
      ]
    }
  },
  mounted() {
    console.log(this.$refs.items)
  }
}
</script>

<template>
  <ul>
    <li v-for="item in list" ref="items">
      {{ item }}
    </li>
  </ul>
</template>

プレイグラウンドで試す

参照の配列では、元の配列と同じ順序が保証されないことに注意する必要があります。

関数を使った参照

ref 属性は、文字列のキーの代わりに、関数にバインドすることもできます。関数はコンポーネントが更新されるたびに呼び出され、要素の参照をどこに保持するかを柔軟に決めることができます。関数は、第 1 引数として要素への参照を受け取ります:

<input :ref="(el) => { /* el をプロパティまたは ref に保持する */ }">

動的な :ref のバインディングを使っていることに注目してください。これにより、参照の名前を示す文字列ではなく、関数を渡すことが可能になります。要素がアンマウントされると、引数は null になります。もちろん、インライン関数のほかに、メソッドを指定することもできます。

コンポーネントでの参照

このセクションでは、コンポーネントの知識があることが前提となります。読み飛ばして、後で戻ってきても大丈夫です。

ref は子コンポーネントに対して使用することもできます。その場合、以下のように、参照はコンポーネントのインスタンスへの参照になります:

<script>
import Child from './Child.vue'

export default {
  components: {
    Child
  },
  mounted() {
    // this.$refs.child は <Child /> のインスタンスを保持します。
  }
}
</script>

<template>
  <Child ref="child" />
</template>

参照されるインスタンスは子コンポーネントの this と同じになります。これは、親コンポーネントからは子コンポーネントのすべてのプロパティとメソッドに完全にアクセスできることを意味します。そうなると、親と子の間で実装の細かな部分が緊密に結合された状態が作られやすくなってしまいます。したがって、コンポーネントの参照は、絶対に必要と言える場合に限って使用するべきです。ほとんどの場合、まずは標準の props と emit のインターフェースを使って親子間のやり取りを実装することを試みるとよいでしょう。

子インスタンスへのアクセスに制限を設けるには、expose オプションを使用します:

export default {
  expose: ['publicData', 'publicMethod'],
  data() {
    return {
      publicData: 'foo',
      privateData: 'bar'
    }
  },
  methods: {
    publicMethod() {
      /* ... */
    },
    privateMethod() {
      /* ... */
    }
  }
}

上の例では、テンプレート参照を用いてこのコンポーネントを参照する親に、publicData と publicMethod のみへのアクセスを許可します。