V4Fire / Client

V4Fire client core library
MIT License
23 stars 14 forks source link

Не обновляется обработчик события на DOM узле при ререндере #1322

Closed shining-mind closed 2 months ago

shining-mind commented 3 months ago

Vue может переиспользовать существующие DOM узлы при ререндере. Если на DOM узле, уже есть обработчик (el._vei), то Vue обновит его проставив ссылку на новую функцию, при этом addEventListener использоваться не будет.

В некоторых случаях vnode узлы генерируются с флагом 32 (HYDRATE_EVENTS), в этом случае Vue не добавит vnode как динамический:

    // данный код выполняется внутри createBaseVNode
    // track vnode for block tree
    if (isBlockTreeEnabled > 0 &&
        // avoid a block node from tracking itself
        !isBlockNode &&
        // has current parent block
        currentBlock &&
        // presence of a patch flag indicates this node needs patching on updates.
        // component nodes also should always be patched, because even if the
        // component doesn't need to update, it needs to persist the instance on to
        // the next vnode so that it can be properly unmounted later.
        (vnode.patchFlag > 0 || shapeFlag & 6 /* ShapeFlags.COMPONENT */) &&
        // the EVENTS flag is only for hydration and if it is the only flag, the
        // vnode should not be considered dynamic due to handler caching.
        vnode.patchFlag !== 32 /* PatchFlags.HYDRATE_EVENTS */) {
        currentBlock.push(vnode);
    }

Мы со своей стороны для vnode задаем проп data-has-v-on-directives, и в patchFlag дописываем 8 (PROPS), однако, так как при создании vnode в createBaseVNode в currentBlock узел не добавляется, то узел все равно будет считаться не динамическим.

Пример

Исходный код

< .&__toggler @click.capture.stop = (e) => actionGuard.protectDomEvent(e, onInitialAddClick.bind(self))

Скомпилированный узел

_createElementVNode.call(_ctx, "div", {
    class: "b-ufo-count-badge__toggler",
    onClickCapture: _cache[0] || (_cache[0] = _withModifiers.call(_ctx, (e) => _ctx.actionGuard.protectDomEvent(e, _ctx.onInitialAddClick.bind(_ctx.self)), ["stop"])),
    "data-cached-class-component-id": "true",
    "data-cached-class-provided-classes-styles": "toggler",
    "data-has-v-on-directives": ""
}, [
    // ...  дочерние узлы 
], 32 /* HYDRATE_EVENTS */ )

Если же @click.capture.stop заменить на @click.stop или @click, то патч флаги не будут заданы вообще и блок все равно будет не динамическим.

shining-mind commented 3 months ago

Потенциально проблема очень серьезная, так как если изначально у узла нет патч флагов и это не компонент, то такой узел отслеживаться не будет (при условии, что он внутри блока).

shining-mind commented 2 months ago

Fixed in #1323