Vue: mouseenter/mouseleave cause unrelated methods to trigger repeatedly

Created on 2 Sep 2020  ·  4Comments  ·  Source: vuejs/vue

Version

2.6.11

<template>
  <div id="app">
    <div>
      <span :id="method1()" />
      <input :id="method2()" type="text" />
    </div>
    <div
      @mouseenter="message = 'huh???'"
      @mouseleave="message = ''"
      style="margin-top: 20px;"
    >
      Hover me, and the 'message' prop will get updated. <br />
      But method1() and method2() are also called.
    </div>
    <h1>{{ message }}</h1>
  </div>
</template>

<script>
export default {
  data: function () {
    return {
      message: ""
    };
  },
  methods: {
    method1() {
      console.log("method1 called");
      return 'first';
    },
    method2() {
      console.log("method2 called");
      return 'second';
    }
  }
};
</script>

Reproduction link

https://codepen.io/rmirabelle/pen/bGpodLx

Steps to reproduce

Open console. Observe output while hovering the target element.

What is expected?

method1 and method2 are called once when loading. When hovering, the message property is updated. Only the h1 element, which outputs the message is updated.

What is actually happening?

The message property is updated. But instead of re-rendering just the h1 that outputs the message, the entire component appears to be re-rendered, thus re-triggering all methods bound to other elements. This occurs seemingly regardless of how the other element attributes are bound. (e.g. .stop, .prevent, etc.)


Given how simple this example is, I feel like I must be missing something fundamental about Vue reactivity. Neither of the elements bound to method1 or method2 observes or uses message. Nor is mouseenter attached to those elements. So why are the methods being called repeatedly?

Most helpful comment

Every Vue component, including the root, has a render function (which is compiled from the template code) and that render function will be called every time the component's state (it's prop, data, and computed properties) gets updated. In your case, updating message is updating the component's state and thus component's render function is getting called. We can take a look at the render function compiled from your code and it's obvious why method1 and method2 is called on every update:

function render() {
    with(this) {
        return _c('div', {
            attrs: {
                "id": "app"
            }
        }, [_c('div', [_c('span', {
            key: "foo",
            attrs: {
                "id": method1()
            }
        }), _c('input', {
            key: "bar",
            attrs: {
                "id": method2(),
                "type": "text"
            }
        })]), _c('div', {
            staticStyle: {
                "margin-top": "20px"
            },
            on: {
                "mouseenter": function($event) {
                    message = 'huh???'
                },
                "mouseleave": function($event) {
                    message = ''
                }
            }
        }, [_v(
            "Hover me, and the 'message' prop will get updated."
        ), _c('br'), _v(
            "But EVERY method will also be called.")]), _c('h1', [
            _v(_s(message))
        ])])
    }
}

_We can easily inspect template compilation result, i.e. the render function, using a nice website: vue-compiler-online. Thanks to @Magiccwl_

Indeed, vue has some diffing algorithm that provides a performant update, trying to prevent unnecessary updates. But this optimization is for DOM operations, in other words, unnecessary DOM operations would be avoided wherever it's possible to do so.

I cannot see the original intention of using methods to produce those two ids, and I would say this could be a [code smell]. That said, assuming there is a plausible reason to do that, [computed properties] might help you with the problem since they're [cached].

All 4 comments

Every Vue component, including the root, has a render function (which is compiled from the template code) and that render function will be called every time the component's state (it's prop, data, and computed properties) gets updated. In your case, updating message is updating the component's state and thus component's render function is getting called. We can take a look at the render function compiled from your code and it's obvious why method1 and method2 is called on every update:

function render() {
    with(this) {
        return _c('div', {
            attrs: {
                "id": "app"
            }
        }, [_c('div', [_c('span', {
            key: "foo",
            attrs: {
                "id": method1()
            }
        }), _c('input', {
            key: "bar",
            attrs: {
                "id": method2(),
                "type": "text"
            }
        })]), _c('div', {
            staticStyle: {
                "margin-top": "20px"
            },
            on: {
                "mouseenter": function($event) {
                    message = 'huh???'
                },
                "mouseleave": function($event) {
                    message = ''
                }
            }
        }, [_v(
            "Hover me, and the 'message' prop will get updated."
        ), _c('br'), _v(
            "But EVERY method will also be called.")]), _c('h1', [
            _v(_s(message))
        ])])
    }
}

_We can easily inspect template compilation result, i.e. the render function, using a nice website: vue-compiler-online. Thanks to @Magiccwl_

Indeed, vue has some diffing algorithm that provides a performant update, trying to prevent unnecessary updates. But this optimization is for DOM operations, in other words, unnecessary DOM operations would be avoided wherever it's possible to do so.

I cannot see the original intention of using methods to produce those two ids, and I would say this could be a [code smell]. That said, assuming there is a plausible reason to do that, [computed properties] might help you with the problem since they're [cached].

BTW, mouseenter/mouseleave are irrelevant here since they're merely something cause component's state to update. It could be button clicks or even periodic timers.

Comopnent state gets update -> render function gets called -> method1 and method2 get called (they're called within render function. Just normal function calls, no magic at all)

This is expected as @Nandiin amazingly explained! Thank you @Nandiin !

This was helpful thanks. My example was contrived to minimize clutter. I incorrectly assumed the method calls would be 'cached' as computed properties are. This example cleanly demonstrates how untrue that is.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

seemsindie picture seemsindie  ·  3Comments

loki0609 picture loki0609  ·  3Comments

franciscolourenco picture franciscolourenco  ·  3Comments

lmnsg picture lmnsg  ·  3Comments

hiendv picture hiendv  ·  3Comments