4
1

Vue3.5の変更内容について詳しく見てみる(useTemplateRef編)

Posted at

はじめに

先日Vue3.5(Tengen Toppa Gurren Lagann)が正式にリリースされました。
3.5では、リアクティブシステムの最適化やSSRのパフォーマンス改善などが行われています。
本記事では3.5の新機能のうち、useTemplateRefに焦点を当て、従来の手法と比較しながらその利点を解説します。

useTemplateRefとは

 useTemplateRefはテンプレート内のDOM要素を取得して操作するための方法をcomposableの形で提供している機能です。
取得したいDOM要素にref属性を設定し、設定した値をuseTemplateRefの引数に渡すことで参照したい要素を取得することができます。

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

const myDiv = useTemplateRef('myDiv')
</script>

<template>
  <div ref="myDiv">This is my div</div>
</template>

Vue3.5以前のやり方

Vue3.5以前もDOM要素の取得自体は行えましたが、refを利用して取得する必要がありました。
refの変数名を取得したいDOM要素のref属性の値と一致させることで、要素にアクセスすることができます。

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

const myDiv = ref(null)

onMounted(() => {
  console.log(myDiv.value)  // DOM要素にアクセス
})
</script>

<template>
  <div ref="myDiv">This is my div</div>
</template>

refで要素を取得することの問題点

refでDOM要素を取得する場合にいくつか問題点がありました。

初見だと分かりづらい

暗黙的に変数名を用いて取得するDOM要素を特定しているため、初めてこの機能を利用する場合に混乱しやすい状態でした。
知らずに変数名を変えてしまい、DOM要素が取得できずに困った人もいるのではないでしょうか。

composableに切り出しづらい

refでDOM要素を取得する場合、必然的に変数名を固定化する必要があります。
これにより、DOM要素に何らかの操作をする処理をcomposableとして切り出したい場合に要素の取得処理自体はcomposableに切り出すことができなくなっていました。

useElementAction.js
// DOM要素にアクセスして何かするcomposable
import { ref, onMounted } from 'vue'

export function useElementAction(elementRef) {
  const doSomethingWithElement = () => {
    console.log(elementRef.value)
    elementRef.value.style.color = 'blue'  // 要素の色を変更するなど
  }

  onMounted(() => {
    if (elementRef.value) {
      doSomethingWithElement()
    }
  })

  return {
    doSomethingWithElement
  }
}
SomeComponent.vue
<script setup>
import { ref } from 'vue'
import { useElementAction } from './useElementAction'

// DOM要素の取得自体はcomposableを使う側で行う必要がある
const myDiv = ref(null)
// 引数でDOM要素自体を渡す
useElementAction(myDiv)
</script>

<template>
  <div ref="myDiv">This is my div</div>
</template>

useTemplateRefでどう改善されたか

取得したい要素のrefの値をuseTemplateRefに渡すだけでよくなったため、要素の取得処理自体もcomposableに切り出せるようになりました。

useElementAction.js
// DOM要素にアクセスして何かするcomposable
import { useTemplateRef, onMounted } from 'vue'

export function useElementAction(refKey) {
  // DOM要素の取得処理もcomposable側に寄せられる!
  const elementRef = useTemplateRef(refKey)

  const doSomethingWithElement = () => {
    console.log(elementRef.value)
    elementRef.value.style.color = 'blue'  // 要素の色を変更するなど
  }

  onMounted(() => {
    if (elementRef.value) {
      doSomethingWithElement()
    }
  })

  return {
    doSomethingWithElement
  }
}
SomeComponent.vue
<script setup>
import { useElementAction } from './useElementAction'

// 操作したいDOM要素のrefの値を文字列渡すだけでいい
useElementAction('myDiv')
</script>

<template>
  <div ref="myDiv">This is my div</div>
</template>

(おまけ)どうやって実装されているか

せっかくなので、useTemplateRefがどのように実装されているかコードを見てみました。
実際のコードはこちら

まず現在のコンポーネントのインスタンスを取得し、refsプロパティを取得(存在しない場合は初期化)しています。

const i = getCurrentInstance()
const r = shallowRef(null)
if (i) {
    const refs = i.refs === EMPTY_OBJ ? (i.refs = {}) : i.refs
    // ...省略
}

次にObject.definePropertyを利用し、useTemplateRefの引数で渡された文字列(key)でプロパティを定義します。

Object.defineProperty(refs, key, {
    enumerable: true,
    get: () => r.value,
    set: val => (r.value = val),
})

これにより、templateのレンダリング時に対象のDOM要素がリアクティブな変数に代入されるようになります。
最後にこのリアクティブな変数を戻り値として返すことで、DOM要素をリアクティブな値として取得できるようになっています。

4
1
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
4
1