127
83

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Vue.js の Composition API における this.$refs の取得方法

Last updated at Posted at 2020-07-30

Vue.js でテンプレート内の DOM 要素や子コンポーネントの参照は、旧来の Options API だと this.$refs で取得できました。
では、Composition API ではどうなっているのでしょうか。

答えは 公式サイトに書いてあります。

公式サイトより引用
<template>
  <div ref="root"></div>
</template>

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

  export default {
    setup() {
      const root = ref(null)

      onMounted(() => {
        // the DOM element will be assigned to the ref after initial render
        console.log(root.value) // <div/>
      })

      return {
        root
      }
    }
  }
</script>

ref 関数で作った変数(上記の場合は root)をテンプレート内の要素の ref 属性に与えます。
初回レンダリング後(onMounted のタイミング)、変数の value プロパティに DOM 要素の参照が代入される、という流れです。

TypeScript で型をつける

さて、このまま終わってしまうのも味気ないので TypeScript ではどう書くのか見ていきましょう。
Vite で作ったプロジェクトをベースにして検証しました。1

DOM 要素の場合

<template>
  <img ref="imgRef" alt="Vue logo" src="./assets/logo.png" />
</template>

<script lang="ts">
import { defineComponent, ref, onMounted } from 'vue'

export default defineComponent({
  setup() {
    const imgRef = ref<HTMLImageElement>() // 1: 型を指定

    console.log(imgRef.value) // 2: undefined が出力される

    onMounted(() => {
      console.log(imgRef.value) // 3: <img>
      console.log(imgRef.value?.clientHeight) // 4: 0 が出力される

      imgRef.value?.addEventListener('load', () => {
        console.log(imgRef.value?.clientHeight) // 5: 200 が出力される
      })
    })

    return { imgRef }
  },
})
</script>
  1. ref 関数は引数なしの場合、ジェネリクスで指定した型と undefined のユニオンになります。2
    上記の例だと imgRef の型は Ref<HTMLImageElement | undefined> です。
  2. レンダリング前は undefined です。
  3. レンダリング後だと img 要素の参照が取得できます。
  4. この時点では画像データはまだ読み込まれていないので高さは 0 です。
    また、imgRef.valueHTMLImageElement | undefined 型なので、?. を使用しています。気になる場合は関数の先頭で if (!imgRef.value) return などを書いて、undefined の可能性を除外するとよいでしょう。
  5. 画像が読み込まれると、正しいサイズが取得できます。

コンポーネントの場合

<template>
  <HelloWorld ref="componentRef" msg="Hello Vue 3.0 + Vite" />
</template>

<script lang="ts">
import { defineComponent, ref, onMounted } from 'vue'
import HelloWorld from './components/HelloWorld.vue'

export default defineComponent({
  components: {
    HelloWorld,
  },
  setup() {
    const componentRef = ref<InstanceType<typeof HelloWorld>>() // 1: 型を指定

    onMounted(() => {
      if (!componentRef.value) return // 型から undefined をなくす

      console.log(componentRef.value) // 2: Proxy {…}

      console.log(componentRef.value.$el) // 3: #text

      console.log(componentRef.value.msg) // 4: Hello Vue 3.0 + Vite
      console.log(componentRef.value.count) // 5: 0

      componentRef.value.count++ // 6: バッドプラクティス!
      console.log(componentRef.value.count) // 1 (増えている)
    })

    return { componentRef }
  },
})
</script>
  1. コンポーネントの実態はコンストラクタなので、このような型になります。
  2. インスタンスの Proxy オブジェクトが入っています。
  3. Vue 3 では $el はテキストノード(<template> 開きタグと最初の DOM 要素の間)になります。3
  4. props のプロパティが直接生えており、値が取得できます(componentRef.value.$props.msg でも同じ)
  5. data も同様です(componentRef.value.$data.count でも同じ)
  6. 外から data の値を変えることもできてしまいますが、メンテナンス性を著しく損なうのでやめましょう。

まとめ

以前にくらべると少し手間は増えましたが、型の恩恵を受けるためなので仕方ないですね。4

今までコンポーネント ref はほとんど使っていませんでした。唯一使いそうな $el がテキストノードに変更されてしまい、さらに使いどころが分からなくなりました。誰か教えて下さい。

  1. 参考になるか分かりませんが、リポジトリはこちら https://github.com/jay-es/composition-api-refs

  2. tsconfig.json"strictNullChecks": true (もしくは "strict": true)にしている場合。
    false になっていると | undefined にはなりません。

  3. Vue 2 + @vue/composition-api の頃はコンポーネント全体の DOM 要素が取得できました。
    Vue 3 でテンプレートのルートに複数の要素を置けるようになったことが関係していると思われます。

  4. 実は Vue 2 + @vue/composition-api の場合、setup 関数の第二引数に refs が生えています。なんとなく理由は分かりますよね。
    ただし、型情報には存在しないため as などでごまかさないといけないのでオススメできません。

127
83
1

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
127
83

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?