
ブログをNext.js14(App Router)でリファクタリングしました
Vue.jsにおいてv-forでHTML要素を繰り返し作成したとき、それぞれの要素に対してテンプレート参照する方法を解説します。ただしVue3のComposition APIのみを対象としています。また、要素の生成から参照するまでのタイミングや挙動、watchとの利用や注意点についてもまとめています。
目次
Vue.jsの3.x系でComposition APIを利用しています。
refではなく、:refのように「:」がついていることに注意して下さい。
そして:refの右辺に関数を代入します。
この関数は次の節できちんと定義するので、この時点で関数の名前は何でも構いません。
<div v-for="i in num" :ref="setDivRef">{{i}}</div>
上のコードの例において、numは要素を繰り返す回数を表すとします。任意の数字に置き換えて下さい。
テンプレート参照をする配列を用意します。
そして①で指定した関数を定義し、その関数の中で引数を配列にpushします。
const divs = ref<HTMLDivElement[]>([])
const setDivRef = (el: any) => {
if (el) {
divs.value.push(el)
}
}
これで配列divsの任意のインデックスにアクセスすれば、v-forで生成された各要素が参照ができます。
例えば、divs.value[0]にアクセスすれば1個目のdiv、divs.value[1]にアクセスすれば2個目のdiv、といった要領です。
また、①と②をまとめて次のようにも書けます。
<div v-for="(n, i) in num" :ref="(el: any) => {if (el) divs[i] = el}">
v-forで繰り返される値が後から増えても、テンプレート参照が働いて配列としてアクセスすることができます。
画面に更新処理が走る度に、テンプレート参照を格納した配列に参照がpushされてしまいます。
v-forとは関係のない部分のDOMの更新であっても、テンプレート参照の配列に同じテンプレート参照が何度も何度もpushされてしまい、意図したものが得られなくなります。
ですので、次のようなコードを書いて更新処理が走る前にテンプレート参照の配列を一旦空にする必要があります。
onBeforeUpdate(() => {
divs.value = []
})
以下v-forを5回繰り返して生成した要素を考え、テンプレート参照が行われるタイミングや動作などを見ていきます。
要素はbeforeMountの後にコードと紐付きます。
const setDivRef = (el: any) => {
if (el) {
divs.value.push(el)
console.log("push")//v-forは5回繰り返す
}
}
console.log('created')
onBeforeMount(() => console.log('beforeMount'))
onMounted(() => console.log('mounted'))
このコードの出力結果は次のようになります。
created
beforeMount
push
push
push
push
push
mounted
確かにbeforeMount〜mountedの間にテンプレート参照がpushされていることが分かります。
したがって、setup直下にコードを書いても(createdのタイミングなので)テンプレート参照した要素にはアクセスできません。
//v-forは5回繰り返すのでdivsには5個要素が入るが
const divs = ref<HTMLElement[]>([])
console.log('created', divs.value.length) // 0になってしまう
マウント後なら次のコードのようにきちんとアクセスできます。
onMounted(() => console.log('mounted', divs.value.length)) //5
テンプレート参照をwatchEffectで監視するケースを考えます。
watchEffectをそのまま利用すると、テンプレート参照がpushされる度にウォッチが作動します。
また、初回(生成時)と2回目以降(push時)の実行タイミングも異なります。初回はcreated後、2回目以降はbeforeMount後です。
console.log('created',divs.value.length)
onBeforeMount(() => console.log('before mount',divs.value.length))
onMounted(() => console.log('mounted', divs.value.length))
watchEffect(() => console.log("watchEffect", divs.value.length))
このコードの出力は次のとおりです。
created 0
watchEffect 0
before mount 0
watchEffect 1
watchEffect 2
watchEffect 3
watchEffect 4
watchEffect 5
mounted 5
watchEffectのflushのタイミングをpostにすると、mountedの直前に1回のみ呼ばれるようになります。
console.log('created',divs.value.length)
onBeforeMount(() => console.log('before mount',divs.value.length))
onMounted(() => console.log('mounted', divs.value.length))
watchEffect(() => console.log("watchEffect", divs.value.length), {flush: 'post'})
このコードの出力は次のとおりです。
created 0
before mount 0
watchEffect 5
mounted 5
確かに、mounted直前に1回だけ呼び出されていることが分かります。
v-forを指定した要素にテンプレート参照をする方法と、使用の際の注意点についてまとめました。
実行タイミングやDOM更新時の注意点には特に気をつけて利用する必要があります。
関連記事
最新の記事
カテゴリー一覧
アーカイブ