Vue 3のCompositional APIでRefをComponentに渡す方法


目的

Compositional APIのrefを別Componentに渡すこと。

背景

React・VueでFormを作る時に、ごちゃごちゃ防止として以下のHTMLを一つのComponentにまとめたい。

<div class="some-class">
 <title>Some Title</title>
 <input type="text" />
</div>

ただ、Componentにrefを渡さなければいけず、ここでややこしくなります。

React.jsだと、forwardRefという関数を使うと、簡単にクリアできますが、Vue 3はそうでもないのです。

ここで、JavaScriptの魔法を使って、解決法を解説します。

InputのComponentを作る

このように作ります。

BaseInput.vue

<template>
  <div>
    <label :for="name">{{ title }}</label>
    <input :type="type" ref="ref" />
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";

export default defineComponent({
  props: {
    type: { type: String, required: true },
    name: { type: String, required: true },
    title: { type: String, required: true },
    inputRef: { required: true },
  },
  setup() {},
});
</script>

FormのComponentも作ろう

NewPost.vue

<template>
  <form @submit="handleSubmit">
    <BaseInput title="Title" name="title" :inputRef="titleRef" type="text" />
    <button type="submit">Submit</button>
  </form>
</template>

<script lang="ts">
import { defineComponent, ref } from "vue";
import BaseInput from "./UI/BaseInput.vue";

export default defineComponent({
  components: {
    BaseInput,
  },
  setup() {
    const titleRef = ref<HTMLInputElement>();

    const handleSubmit = (event: Event) => {
      event.preventDefault();
      if (titleRef.value) {
        console.log(titleRef.value.value);
      }
    };

    return {
      handleSubmit,
    };
  },
});
</script>

JS魔法:渡したいrefを関数に包む

JavaScriptの信者じゃなければ納得はいかんと思います。

NewPost.vueのsetupがreturnするObjectに、titleRefを返す関数を入れるのです。

    return {
      titleRef: () => titleRef,
      handleSubmit,
    };

ここでBaseInput.vueに戻って、この関数を実行した上でTemplateに渡します。

setupにpropsを入れます。

BaseInput.vue

<template>
  <div>
    <label :for="name">{{ title }}</label>
    <input :type="type" ref="ref" />
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";

export default defineComponent({
  props: {
    type: { type: String, required: true },
    name: { type: String, required: true },
    title: { type: String, required: true },
    inputRef: { type: Function, required: true },
  },
  setup(props) {
    return {
      ref: props.inputRef(),
    } 
  },
});
</script>

まとめ

これでCompositional APIのrefを渡してスッキリしたFormを作れます!

本当は、ReactみたいにちゃんとしたforwardRef機能をつけてほしいところですね。