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 thebox1theiThe label,expandbecomefasle.box1Hidden,box2Display,countA+ 1
  • And then clickbox2.expandbecometrue.box2Hidden,box1Display,countB+ 1

Facts:

  • Click on thebox1theiThe label,expandbecometrue.box1Display,box2Hidden,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

  1. Vue tries to reuse nodes when updating
  2. Event capture – bubbling is a microtask
  3. 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.