Search code examples
javascriptvue.jsvuex

How to prevent component from collecting unneccessary dependencies?


I have a component that is based on some nested state. E.g.

<rect width="some.deep.state.width">

Whenever any object in this path is altered (not newly set), the component re-renders even though nothing changed.

For example, the following leads to a rerender:

Vue.set(some.deep, 'newProp', something)

One can imagine, that this is highly undesired especially when using nested state in a store and some array values change.

In my case I, save multiple entities in my store and whenever a new instance of that entity is added, all components based on that entity rerender even though nothing changed.

So is there a way to prevent collecting this (technical) deps (pun intended)? Can I prevent Vue from collecting deps by some mechanism?

PS: I know, that this mechanism is useful in other places. In my case, it is not desired

Codesandbox which shows that behavior (in the console): https://codesandbox.io/s/vuejs-playground-forked-sx534?file=/components/hello.js

Inline Example - update is triggered:

const app = new Vue({
  el: '#app',
  data: function () {
    return { some: { nested: { prop: 6 }}}
  },
  created: function () {
    console.log('component created')
    
    setTimeout(function () {
    
      Vue.set(this.some, 'otherProp', 10)
    
    }.bind(this), 2000)
  },
  updated: function () {
    console.log('component updated')
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
 Prop: {{ some.nested.prop }}
</div>


Solution

  • The way I ended up doing it was by watching the deeply nested properties. Watchers seem to not register for changes I explained in my question. Therefore I added a watcher for every property which sets a property on the component which in turn I can use in the template.

    This solved the problem of unnecessary re-renders, however, it turned out that multiple watchers whose values change simultaneously each trigger a rerender. This is strange since all watchers execute in one dom cycle and therefore should only trigger one rerender. If anyone knows the reason for this, feel free to elaborate!

    const app = new Vue({
      el: '#app',
      data: function () {
        return {
          some: { nested: { prop: 6 }},
          theProp: null
        }
      },
      created: function () {
        console.log('component created')
        
        this.$watch(() => this.some.nested.prop, (newValue) => this.theProp = newValue, {immediate: true})
        
        setTimeout(() => {
        
          // Does not triggers update
          Vue.set(this.some, 'otherProp', 10)
        
        }, 2000)
        
        setTimeout(() => {
        
          // Triggers update
          this.some.nested.prop = 10
        
        }, 4000)
      },
      updated: function () {
        console.log('component updated')
      }
    })
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
    <div id="app">
     Prop: {{ theProp }}
    </div>