Vue.js(v3)のv-forでrefを使う方法(Composition API)

Vue.js Vue.js

Vue.jsのComposition APIを使っているコンポーネントで、v-forをしている箇所に対して、refを使うときにハマりました。
やりたかったのはv-forで複数定義しているテキストエリアをrefで参照して、値を更新するという処理です。

この記事には、Composition APIを使ってv-forをしている箇所でrefを使って要素を取得して、更新する方法を書いています。
公式の記事を探してみると、Vue.js(v3)のv-forを使っている箇所のrefについて下記の記載がありました。
https://v3.ja.vuejs.org/guide/migration/array-refs.html

v-forでrefを使う

実際にVue.js(v3)のComposition APIを使用している実装例を書いて挙動を確認してみます。

子コンポーネントの実装例

v-forのループ中に表示するコンポーネントを下記のように実装しました。
InputTextArea.vueという名前で作成しました。

<template>
  <textarea class="border border-2 border-blue-500 rounded-lg p-3" placeholder="input textarea..." v-model="textArea"></textarea>
</template>
<script>
import { ref } from 'vue'
export default ({
  props: ['paramText'],
  setup(props) {
    const textArea = ref(props.paramText)

    return {
      textArea
    }
  },
})
</script>

子コンポーネントの解説

テキストエリアのみを表示するコンポーネントです。
v-modelをtextAreaという名前で作成しています。

親コンポーネントから受け渡されたparamTextというパラメータを、v-modelのtextAreaに設定します。
そうすることで、テキストエリアに親のパラメータが表示されます。

親コンポーネントの実装例

子のコンポーネントをループして表示させるコンポーネントとして、下記を用意しました。

<template>
  <div id="app">
    <div class="m-5">
      <div v-for="(textValue, index) in textValues" :key="index" :ref="textAreaRef">
        <input-text-area :paramText="textValue" />
      </div>
      <button @click="test" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-full">
        テスト!
      </button>
    </div>
  </div>
</template>

<script>
import InputTextArea from './InputTextArea.vue'
import { ref } from 'vue'
export default {
  components: {
    InputTextArea
  },
  setup () {
    const textValues = ref([])
    textValues.value = ['apple', 'banana', 'grape', 'orange', 'peach']

    const textAreaItems = ref([])
    const textAreaRef = (el) => {
      if (el) {
        textAreaItems.value.push(el)
      }
    }

    const test = () => {
      textAreaItems.value[2].querySelector('textarea').value = "test!!!!"
      textAreaItems.value[4].querySelector('textarea').value = "test!!!!"
    }

    return {
      textValues,
      textAreaRef,
      textAreaItems,
      test
    }
  }
}
</script>

親コンポーネントの解説

子コンポーネントを下記のようにv-forでループして表示しました。
表示している内容は果物の英語名を適当に配列(textValues)に入れて、子コンポーネントのparamTextとして渡して表示しています。

<div v-for="(textValue, index) in textValues" :key="index" :ref="textAreaRef">
  <input-text-area :paramText="textValue" />
</div>

重要な箇所はここで:refを使用することでv-forで表示しているHTML要素をひとつづつ取得することができるということです。

const textAreaItems = ref([])
const textAreaRef = (el) => {
  if (el) {
    textAreaItems.value.push(el)
  }
}

textAreaItemsという配列を用意して、textAreaRefで受けたHTML要素の内容を配列に入れています。
これはsetupが終わった(初期表示終了)時点で入ってくるようです。

後はボタンを押したときに、下記の処理を行ってテキストエリアの内容を書き換えてみています。
テキストエリアの3番目と5番目の内容がtest!!!!に変わります。

const test = () => {
  textAreaItems.value[2].querySelector('textarea').value = "test!!!!"
  textAreaItems.value[4].querySelector('textarea').value = "test!!!!"
}

querySelectorで変更した内容をv-modelに反映する

上記実装までで、refで指定しているテキストエリアの中身をquerySelectorで変更することができました。
実はこれではv-modelに反映されていない状態になります。

そして、このままの状態でv-modelの内容を使って更新や追加などを行う場合ですが、内容が変わっていないため困ることがありました。
このような場合に、v-modelにも内容を反映するには下記のようにします。

textAreaItems.value[2].querySelector('textarea').value = "test!!!!"
textAreaItems.value[2].querySelector('textarea').dispatchEvent(new Event('input'))

これでquerySelectorから変更した場合にもv-model(textArea変数)に値を反映することができます。

動作確認

実際に動作を確認してみます。
初期では下記のように、配列に入れた果物の名前がずらっと入っています。

ボタンを押すと…

このように想定通り、querySelectorを使用して、テキストエリアの内容を変えることができました!

コメント

タイトルとURLをコピーしました