Subscribe on changes!

:ref="someFunc" triggered twice for the same Element

avatar
Sep 21st 2021

Version

3.2.13

Reproduction link

sfc.vuejs.org/

Steps to reproduce

  1. Setup the vue template with a HTML Element
  2. add :ref="dom" to that HTML Element
  3. create the composition API dom(ref) for :ref="dom" : it is to show messages for the dom when it is assigned to the ref input
  4. load the page

What is expected?

only 1 triggering of dom(ref) input.value changes from null to [HTMLInputElement] only

What is actually happening?

after 1st triggering, there is another triggering for the same reference

2nd triggering:

input.value changes from [HTMLInputElement] to [HTMLInputElement] and these two HTML Elements are identical


Same some redundant callings in Vue3 happened for ref

avatar
Sep 21st 2021

see #4084

This is expected, setRef will execute on each time patch and unmount.

I think your case is okay, since the content or prop is changed.

But to my case here, there is no any change to the DOM. There is only one rendering. ( just mount to the page and that's all )

avatar
Sep 21st 2021

hm ok, that's weird then.. if you switch to script-setup it works as you intended demo. Anyway, whether it's bug or not it's easy to fix just use onMounted hook.

avatar
Sep 22nd 2021

image This will only be run once but I don't know if it's necessary

avatar
Sep 22nd 2021

The issue is that the assignment of the ref is done during rendering, where we still collect reactive dependencies of the render/template.

In the provided example, you read input.value, which makes it a reactive dependency of the template. Then you change input.value, which causes a re-render, as a reactive dependency of the template changed.

To see this in action, just comment out the assgnment:

let was // = input.value

Now it will run only once.

As @zhangenming showed, we could work around that by pausing tracking during the ref assignment, but as of now I'm not sure if that is a problem for other use cases and/or should be considered a breaking change.

avatar
Sep 22nd 2021

If the programmers do not know :ref="someFunc" is called for any change including the value of ref, it would be a disaster of the whole application.

Recursive Calling due to this issue image

image

(I think 102 is just because the playground stopped the calling)

avatar
Sep 22nd 2021

That's expected behavior because the initial ref is empty before render, and is changed on first render when the element is created. Your ref function accesses the value which registers it as a dependency of your render process, so the render will run again because you mutated a dependency right afterwards. The dependency collection should not be disabled inside ref functions because it's possible you want to dynamically determine which ref to put the value into.

Your hypothetical case is unlikely to happen in real world cases. Similar infinite loops have always been possible by doing the same thing inside render functions or watchers.

avatar
Oct 8th 2021

I made a solution for this issue.

SFC Playground - non-reactive refDom

image

refDom object is intentionally not vue-reactive such that it can avoid the recursive behavior due to variable changing and can be used inside reactive() too

use onMounted is much proper but it sometimes the developer wants to watch the immediate change in ref

This implementation can use for changing ref inside vue-reactive object too

<template v-for="entry in entries" :key="entry.key">
  <div :ref="entry.div.setRef">My Div {{entry.key}}</div>
</template>
const entries = reactive([
  {
    key: 1,
    div: refDom(),
  },
  {
    key: 2,
    div: refDom()
  }
])

As it is non-vue-reactive, watch(entry, ()=>{...}) would not fire for any dom ref change.