Vue3.0 series – Responsive
Vue3.0 series – rendering process
Vue3.0 series – New features
preface
This is not really vuE3, but isse in vue2. I found the issue while reading the source code of VUe3, but I found it interesting, so I will record it here
Link: issue
The problem
The code is as follows:
<div class="box1" v-if="expand">
<i @click="expand = false, countA++">Expand is True</i>
</div>
<div class="box2" v-if=! "" expand" @click="expand = true, countB++">
<i>Expand is False</i>
</div>
<div>
countA: {{countA}}
</div>
<div>
countB: {{countB}}
</div>
Copy the code
Normal condition:
- Click on the
box1
thei
The label,expand
becomefasle
.box1
Hidden,box2
Display,countA
+ 1 - And then click
box2
.expand
becometrue
.box2
Hidden,box1
Display,countB
+ 1
Facts:
- Click on the
box1
thei
The label,expand
becometrue
.box1
Display,box2
Hidden,countA
+ 1,countB
+ 1 - Click on it again
You can go to the Reproduction Link to have a look
The reasons causing
From the above results, it looks as if a click triggers both box1 and Box2’s click events. Why is that?
Yudada replied in the issue:
So, this happens because:
The inner click event on <i> fires, triggering a 1st update on nextTick (microtask)
The microtask is processed before the event bubbles to the outer div. During the update, a click listener is added to the outer div.
Because the DOM structure is the same, both the outer div and the inner element are reused.
The event finally reaches outer div, triggers the listener added by the 1st update, in turn triggering a 2nd update.
This is quite tricky in fix, and other libs that leverages microtask for update queueing also have this problem (e.g. Preact). React doesn't seem to have this problem because they use a synthetic event system (probably due to edge cases like this).
To work around it, you can simply give the two outer divs different keys to force them to be replaced during updates. This would prevent the bubbled event to be picked up
Copy the code
What does that mean? It means,
After clicking on the I TAB, the event bubbles up to the Box1 TAB,
But bubbling is also a microtask, even after nextTick.
The click event of box1 is bound to box1 as well as the functions and children of Box1. The click event of box2 is bound to Box1 as well as the functions and children of Box1.
The event then bubbles into box1’s DOM, triggering the Click event. Hence the above phenomenon.
So you can handle this by adding a key.
Check the code in VUe3 to see if it is the same node:
function isSameVNodeType(n1: VNode, n2: VNode) :boolean {
return n1.type === n2.type && n1.key === n2.key
}
Copy the code
The solution
Vue3 doesn’t have this problem because vue3 adds a default key to v-if and doesn’t reuse nodes.
So how does that work in VUe2?
Vue wraps a layer around the Event callback, returning an invoke, and the subsequent remove event is also a wrapper function for removal:
function createInvoker(
initialValue: EventValue,
instance: ComponentInternalInstance | null
) {
const invoker: Invoker = (e: Event) = > {
const timeStamp = e.timeStamp || _getNow()
if (timeStamp >= invoker.attached - 1) {
callWithAsyncErrorHandling(
patchStopImmediatePropagation(e, invoker.value),
instance,
ErrorCodes.NATIVE_EVENT_HANDLER,
[e]
)
}
}
invoker.value = initialValue
invoker.attached = getNow()
return invoker
}
Copy the code
InitialValue is the binding event callback.
You can see that the Invoke function has an attached variable whose value is the timestamp when vue does a render or update
When the event callback is triggered, the system checks whether the timestamp triggered by the event is larger than the cached timestamp, or the callback is triggered.
If it is a bubbling event, the timestamp of the event is unchanged because of the catch-trige-bubble sequence, whereas vUE triggers update and re-executes patchEvent, creating a new Invoke wrapper function. Ttached values are updated so that the bubbled timestamp is smaller than the event timestamp in the Invoke cache, meaning that the event is not fired repeatedly.
conclusion
From this issue, we can better understand the following three knowledge points
- Vue tries to reuse nodes when updating
- Event capture – bubbling is a microtask
- Event capture – trigger – bubble sequence, e is the same
This is supposed to be the last of vue3.0 series, and I will add it later if I have some ideas. Also is finally completed to vue3.0 source code after reading the summary.
You are also welcome to point out problems and make suggestions.