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

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

## useTemplateRefとは
　useTemplateRefはテンプレート内のDOM要素を取得して操作するための方法をcomposableの形で提供している機能です。
取得したいDOM要素にref属性を設定し、設定した値をuseTemplateRefの引数に渡すことで参照したい要素を取得することができます。
```vuejs
<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属性の値と一致させることで、要素にアクセスすることができます。
```vuejs
<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に切り出すことができなくなっていました。
```js: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
  }
}
```
```vuejs: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に切り出せるようになりました。
```js: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
  }
}
```
```vuejs:SomeComponent.vue
<script setup>
import { useElementAction } from './useElementAction'

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

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

## （おまけ）どうやって実装されているか
せっかくなので、useTemplateRefがどのように実装されているかコードを見てみました。
（[実際のコードはこちら](https://github.com/vuejs/core/blob/main/packages/runtime-core/src/helpers/useTemplateRef.ts)）

まず現在のコンポーネントのインスタンスを取得し、refsプロパティを取得（存在しない場合は初期化）しています。
```js
const i = getCurrentInstance()
const r = shallowRef(null)
if (i) {
    const refs = i.refs === EMPTY_OBJ ? (i.refs = {}) : i.refs
    // ...省略
}
```
次にObject.definePropertyを利用し、useTemplateRefの引数で渡された文字列（key）でプロパティを定義します。
```js
Object.defineProperty(refs, key, {
    enumerable: true,
    get: () => r.value,
    set: val => (r.value = val),
})
```
これにより、templateのレンダリング時に対象のDOM要素がリアクティブな変数に代入されるようになります。
最後にこのリアクティブな変数を戻り値として返すことで、DOM要素をリアクティブな値として取得できるようになっています。