w3resource

Reactivity in Depth

It is time to take a deep dive! Vue's unobtrusive reactivity system is one of its distinct features. Models are plain JavaScript objects. When we modify them, the view will update. This makes state management simple and intuitive, however, it is also important to understand how it works to avoid some common gotchas. We are going to dig into some of the lower-level details of Vue's reactivity system in this section.

How Changes Are Tracked

When we pass plain JavaScript object to a Vue instance as its data option, Vue will walk through all of its properties and convert these properties to getters/setters using Object.defineProperty.

This is an un-shimmable and ES5-only feature, this is why Vue doesn't support IE8 and below.

The getters/setters are invisible to users, however, under the hood they enable Vue to perform dependency-tracking and change-notification when the properties are accessed or modified.

A caveat is that browser consoles format getter/setters differently when converted data objects are logged, so we may want to install vue-devtools for a more inspection-friendly interface.

Every component instance always has a corresponding watcher instance, that records any properties "touched" during the component's render as dependencies. Later on when the setter of a dependency is triggered, it will notify the watcher, this will in turn causes the component to re-render.

Change Detection Caveats

Due to the limitations that exists in modern JavaScript (and the abandonment of Object.observe), Vue cannot detect property addition or property deletion. Since Vue will perform the getter/setter conversion process during instance initialization, a property will need to be present in the data object in order for Vue to convert it and make it reactive. For example:

var vm = new Vue({
  data: {
    a: 1
  }
})
// `vm.a` will now be reactive

vm.b = 2
// `vm.b` will NOT be reactive

Vue does not allow us to add new root-level reactive properties to an already created instance dynamically. However, we can add reactive properties to a nested object using the Vue.set(object, propertyName, value) method:

```Vue.set(vm.someObject, 'b', 2)

We can also use the vm.$set instance method, this is an alias to the global Vue.set:

this.$set(this.someObject, 'b', 2)

Sometimes we may want to assign a number of properties to an existing object, for instance using Object.assign() or _.extend(). However, new properties that are added to the object will not trigger changes. In such cases, create a fresh object with properties from both the mixin object and the original object:

// instead of `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })

Declaring Reactive Properties

Since Vue does not allow us to dynamically add root-level reactive properties, we have to initialize Vue instances by declaring all root-level data properties upfront, if the property does not have a value, use an empty value:

var vm = new Vue({
  data: {
    // declare message with an empty value
    message: ''
  },
  template: '<div>{{ message }}</div>'
})
// set `message` later
vm.message = 'Hello!'

failure to declare message in the data option, will cause Vue to warn us that the render function is trying to access a property that doesn't exist.

This will eliminate a class of edge cases in the dependency tracking system and equally make Vue instances play nicer with type checking systems.

Async Update Queue

Vue DOM updates are performed asynchronously. When a data change is observed, it will open a queue and then buffer all the data changes that happen in the same event loop. In cases where the same watcher is triggered multiple times, it will only be pushed into the queue once. This buffered de-duplication is important to avoid unnecessary calculations and DOM manipulations. Then, Vue flushes the queue in the next event loop "tick", and proceeds to perform the actual (already de-duped) work. Internally Vue tries native Promise.then, setImmediate, and MutationObserver for the asynchronous queuing and then falls back to setTimeout(fn, 0).

For instance, when we set vm.someData = 'new value', the component will not re-render immediately. This will update in the next "tick", when the queue is flushed. Most of the time we do not need to care about this, but it can be tricky when we want to do something that depends on the post-update DOM state. Although Vue.js encourages developers to think in a "data-driven" fashion and avoid directly touching the DOM, there are times it might be necessary to get our hands dirty. In order for us to wait until Vue.js has finished updating the DOM after a data change, we can use Vue.nextTick(callback) immediately after the data is changed. The callback will then be called after the DOM has been updated. Here is an example:

<div id="example">{{ message }}</div>
  el: '#example',
  data: {
    message: '123'
  }
})
vm.message = 'new message' // change data
vm.$el.textContent === 'new message' // false
Vue.nextTick(function () {
  vm.$el.textContent === 'new message' // true
})
The vm.$nextTick() instance method also exists, this is especially handy inside components, because it does not need global Vue and the this context of its callback?s will be automatically bound to the current Vue instance:

```Vue.component('example', {
  template: '<span>{{ message }}</span>',
  data: function () {
    return {
      message: 'not updated'
    }
  },
  methods: {
    updateMessage: function () {
      this.message = 'updated'
      console.log(this.$el.textContent) // => 'not updated'
      this.$nextTick(function () {
        console.log(this.$el.textContent) // => 'updated'
      })
    }
  }
})

Since $nextTick() will return a promise, we can achieve the same as the above using the new ES2016 async/await syntax:

```methods: {
  updateMessage: async function () {
    this.message = 'updated'
    console.log(this.$el.textContent) // => 'not updated'
    await this.$nextTick()
    console.log(this.$el.textContent) // => 'updated'
  }
}

Previous: Server-Side Rendering
Next: Migrating from Vue 1.x



Follow us on Facebook and Twitter for latest update.