Vue: Memory leak with component with input with v-model

Created on 9 May 2019  Â·  19Comments  Â·  Source: vuejs/vue

Version

2.6.10

Reproduction

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    <script src="https://unpkg.com/vue"></script>

    <div id="app">
      <div id="nav">
        <button @click="goHome">go to Home</button>
        <button @click="goAbout">go to About</button>
      </div>
      <component :is="current"></component>
    </div>

    <script>
      const Home = {
        name: 'Home',
        template: `
      <div>
        <h2>Home</h2>
      </div>
      `,
      }

      const About = {
        template: `
        <div class="about">
    <h1>This is an about page</h1>
    <input type="text" v-model="input">
  </div>
      `,
        name: 'about',
        data: () => ({
          input: '',
        }),
      }

      const vm = new Vue({
        el: '#app',
        data() {
          return {
            current: 'Home',
          }
        },

        methods: {
          goHome() {
            this.current = 'Home'
          },
          goAbout() {
            this.current = 'About'
          },
        },
        components: { Home, About },
      })
    </script>
  </body>
</html>

Screen Shot 2019-05-09 at 18 59 07

Steps to reproduce

  • go to the about page
  • type in the input
  • leave the page
  • collect garbage and take a snapshot with devtools

What is expected?

VueComponent count should be stable

What is actually happening?

VueComponent count keeps increasing.


seems to be related to typing in the input

bug has PR

Most helpful comment

@posva, i just found out PR #10085 can fix it, my Chrome Version is 74.0.3729.169.

All 19 comments

@posva After further testing. This might not be Vue's fault.

Testing the code above on Chrome Version 72.0.3626.121 (mac) seems to run without any issues and VueComponent count is stable.

it isn't in 74. It could be a bug on Chrome. I've found leaks in the past (https://bugs.chromium.org/p/chromium/issues/detail?id=949587). I didn't test with this one though as it looks like Vue is retaining the component and the browser cannot free it

I can confirm this happens in Chrome 74. When trying to replicate this in Safari, I cannot even see any Vue related objects in their JS allocations inspector, but that is probably just my mistake somehow.

However, if this doesn't happen in the earlier versions of Chrome with the same Vue version, this could be probably forwarded to Chromium project and closed here?

This is especially critical when we talk about large SPA's with tones of data. As far as I can see Chrome 74 is not able to collect much...

https://bugs.chromium.org/p/chromium/issues/detail?id=961494

There is some development regarding this issue.

Recently I have found that there is a problem while input element work with 'v-model'. If I type the content quickly, it cause the high CPU usage.
3E5A8D54478379E333140DDA975D8E0F

@posva or anyone knows how to test this with FF devtools and EDGE devtools?
Because we don't have the constructors names in those.
This is happening in other browsers like chrome and ff too.
Thanks.

@posva, i just found out PR #10085 can fix it, my Chrome Version is 74.0.3729.169.

Thanks for checking it out!

Does anybody know if this also happens in the PROD version? As I am not able to filter by "vue" components in Chrome DevTools. I see the same code as the dev version though...

Does anybody know if this also happens in the PROD version? As I am not able to filter by "vue" components in Chrome DevTools. I see the same code as the dev version though...

@clopezcapo Yes it does.

Bufff, and isn't that a huge issue for PROD environments? It looks like is a serious potential killer to me.

Does anybody know if they are aware?

@posva can you please update us on the status of this fix? As still happens in PROD.

this issue is too serious, it take me a long time to find it out...

So... any workarounds so far? Downgrade to v2.6.9?

I can confirm that PR #10085 fixes it for us as well. Is there a plan to merge this?

I also tried this solution, but only half of it worked. chrome80/vue2.6.11/vue-router 3.0.
While vue component has been successfully recycled, three additional listeners have not been removed and dom cannot be recycled. Looking at the heap snapshot,the VueComponent increment is 0, but htmlInputElement has not been recycled, as Performance tools proves. I tracked that these 3 listeners comes from "vue/src/platforms/web/runtime/directs/model.js"-->function onCompositionStart and onCompositionEnd, But I'm not familiar with vue source code .
when input element is deep in a large component, this leads to a large memory leak.

Excuse me?When can I wait until this problem is corrected?There is a serious memory leak in my project, and is in urgent need of a new version!

As a workaround, I wrap my inputs to:

  • manually add / remove event listeners
  • 'detach' the input from component DOM on beforeDestroy

This way 'only' the inputs (which have been edited) itself leak and not the whole component / view.

Simplified:

<template>

  <div id="nativeInputContainer">
    <input
      ref="nativeInput"
      id="nativeInput"
      v-bind:type="(password) ? 'password' : ''"
      v-bind:placeholder="placeholder"
      v-bind:value="workingValue"
    />
  </div>

</template>

<script>

export default {
  name: 'BaseInput',
  inheritAttrs: false,
  components: {},
  props: {
    placeholder: {
      type: String,
      required: false,
      default: () => { return '' }
    },
    value: {
      type: String,
      required: true
    },
    password: {
      type: Boolean,
      required: false,
      default: () => { return false }
    }
  },
  data () {
    return {
      workingValue: ''
    }
  },
  watch: {

    value: {
      immediate: true,
      handler () {
        this.workingValue = this.value
      }
    }

  },
  methods: {

    onInput (event) {
      this.workingValue = event.target.value
      this.$emit('input', this.workingValue)
    },

    onChange (event) {
      this.$emit('change', this.workingValue)
    }

  },
  mounted () {
    // to avoid memory leak (from vue)
    // we assign event listeners manually
    this.$nextTick(() => {
      this.$refs.nativeInput.addEventListener('input', this.onInput)
      this.$refs.nativeInput.addEventListener('change', this.onChange)
    })
  },
  beforeDestroy () {
    // to avoid memory leak (from vue)
    // we remove event listeners manually
    this.$refs.nativeInput.removeEventListener('input', this.onInput)
    this.$refs.nativeInput.removeEventListener('change', this.onChange)

    // to avoid memory leak (from input itself)
    // which causes 'this' to be not available for gc
    // (undo history keeps input alive)
    // see: https://bugs.chromium.org/p/chromium/issues/detail?id=961494#c14
    this.$el
      .querySelector('#nativeInputContainer')
      .removeChild(
        this.$el.querySelector('#nativeInput')
      )
    // maybe not necessary but "always Double Tap"
    this.$refs.nativeInput = null
  }

}
</script>

<style scoped>
</style>
Was this page helpful?
0 / 5 - 0 ratings

Related issues

bdedardel picture bdedardel  Â·  3Comments

hiendv picture hiendv  Â·  3Comments

lmnsg picture lmnsg  Â·  3Comments

aviggngyv picture aviggngyv  Â·  3Comments

paceband picture paceband  Â·  3Comments