Incorrect template ref
Version
3.0.4
Reproduction link
https://codesandbox.io/s/misty-tree-xzmh1?file=/src/main.js
Steps to reproduce
Open the link, check the console
What is expected?
The template ref should be <div>hello</div>
What is actually happening?
The template ref is <div>foo</div>
Is this because the <div>
is being reused across renderings and the console logging shows the current content of that <div>
, not the content it had when it was logged? The content of that <div>
gets updated when it re-renders. You'd need to give it a key
for the diffing to pair up the correct nodes.
Using console.log(refDom.value.innerHTML);
seems to give the correct value.
I think this is a bug, if you replace patchKeyedChildren with patchUnkeyedChildren
, then everything will work fine.
Check this link, the click event will be bound to div(foo)
: https://codesandbox.io/s/throbbing-snowflake-vpm1x, but when mounted, patch has not been performed yet, let alone reuse, the div(foo)
doesn't exist yet, right?
In this reproduction, if you remove the refVal.value && h('span', 'bar')
sentence, it will also work correctly, I tracked this issue and its update process is as follows:
The pre-vnode:
[
prev-comment1,
prev-div(hello),
prev-comment2
]
The next-vnode:
[
next-div(foo),
next-div(hello),
next-div(bar)
]
- remove the
prev-comment1
- remove the
- patch:
prev-div(hello) ---> next-div(foo)
- patch:
- remove the
comment2
- remove the
- create:
next div(bar)
- create:
- create:
next div(hello)
- create:
But I don’t know what caused the problem, it's weird, just like @edison1105 said.
I can't follow.
The div will be re-used as it doesn't have a key. That's expected behavior (or did I miss an optization in Vue 3? Totally possible!).
Since it's being re-used, the logged element will first contain 'hello'
, but the component will immediately update due to the changed ref value to true
, so it will only show 'hello'
for a millisecond, not enough to catch it with the eyes.
So in that update, the element will be patched to now contain 'foo'
, and the logged element, which is still the same same element, will show as 'foo'
.
So in short I don't understand what the bug is supposed to be.
The immediate reason is that setRef
is async, so that the refDom.value
has a preValue on the first frame.
https://github.com/vuejs/vue-next/blob/07559e5dd7e392c415d098f75ab4dee03065302e/packages/runtime-core/src/renderer.ts#L344-L358
You can change the onMounted
callback like code blow to verify it in reproduction link https://codesandbox.io/s/throbbing-snowflake-vpm1x provided by @qq397023775
onMounted(() => {
refDom.value.addEventListener("click", () => {
console.log("what????");
});
console.log(refDom.value)
setTimeout(() => {
console.log(refDom.value)
})
refVal.value = true;
});
And then you can find that the refDom.value
is div(foo)
on the first frame and div(hello)
on the next frame.
But the root cause i think is incorrect element reusing when patch , i will create a PR to take up.
By the way, as a workround you can wrap the dom handle into nextTick
.
But the root cause i think is incorrect element reusing when patch
@luwuer
This problem has nothing to do with patch
, please note that we have already had the issue in the onMounted
hook, and no patch has been performed yet.
Hook mounted
is trigger after patch
.
Hook mounted is trigger after patch.
Completely wrong, the patch here refers to the update triggered after changing the reactive data. the mounted
is always before the updated
.
Completely wrong, the patch here refers to the update triggered after changing the reactive data. the
mounted
is always before theupdated
.
Its ok mounted
before update
.
But there one thing i didnt make clear that the refDom.value
is correct when mounted
(alreay patch), and go wrong when
patch before updated
hook(dom children were changed). To verify it you can remove code refVal.value = true
.
So i said:
i think is incorrect element reusing when patch
Your solution is not correct. There is no problem in reusing nodes, because pre-div(hello)
and next-div(foo)
should be reusable, because their type(div
) and key(undefined
) are the same, which has nothing to do with ref
dom.
Your solution is not correct. There is no problem in reusing nodes, because pre-div(hello) and next-div(foo) should be reusable, because their type(div) and key(undefined) are the same, which has nothing to do with ref dom.
yep, consensus.
But if resuing when there has ref will lead to this issue, and the refDom.value
will be changed nextTick.
Also,resuing without ref assertion will change the uid
of VNode of div(hello)
. This seem not to be a principle of reuse.
The problem you see comes from logging in the console the HTML node, but the variable contains the right HTMLElement all the time: the div
with the hello
text. At some point, the node gets reused (as mentioned in a comment above) but the ref ends up pointing to the correct node. If you manage to reproduce it where the ref points to the wrong element, open a new issue with a breaking example.
Looking at https://vue-next-template-explorer.netlify.app/ to compare the output render function with the one you manually wrote could also make it easier to expose the bug
The problem you see comes from logging in the console the HTML node, but the variable contains the right HTMLElement all the time: the
div
with thehello
text. At some point, the node gets reused (as mentioned in a comment above) but the ref ends up pointing to the correct node.
Ref pointing to wrong HTMLElement at one tick, actually. @posva
onMounted(() => {
refDom.value.addEventListener("click", () => {
console.log("what????");
});
// --- notice console log ---
console.log(refDom.value)
setTimeout(() => {
console.log(refDom.value)
})
// --- notice console log ---
refVal.value = true;
});
This example is in a comment above.