Now it’s time to dig deeper! One of the most unique features of Vue is its non-invasive responsive system. Data models are just plain old JavaScript objects. And when you modify them, the view is updated. This makes state management very straightforward, but it’s also important to understand how it works so you can avoid some common problems. In this chapter, we’ll look at the low-level details of Vue responsive systems.

How to track change

Watch video tutorials on Vue Mastery

When you pass an ordinary JavaScript object into a Vue instance as the data option, Vue iterates through all of the object’s properties, And use Object.defineProperty to turn all of these properties into getters/setters. Object.defineproperty is a non-shim feature in ES5, which is why Vue does not support IE8 and earlier browsers.

These getters/setters are invisible to the user, but internally they allow Vue to track dependencies and notify changes when the property is accessed and modified. It is important to note that different browsers format getters/setters differently when printing data objects on the console, so it is recommended to install vue-devTools for a more user-friendly interface for examining data.

Each component instance corresponds to a Watcher instance, which records “touched” data properties as dependencies during component rendering. Watcher is then notified when the setter for the dependency fires, causing its associated component to be re-rendered.

Precautions for detecting changes

Due to JavaScript limitations, Vue cannot detect array and object changes. However, there are ways to circumvent these limitations and make them responsive.

For the object

Vue cannot detect the addition or removal of property. Since Vue performs getter/setter conversions on property when it initializes the instance, the property must exist on the data object for Vue to convert it to reactive. Such as:

Var vm = new Vue({data:{a:1}}) // 'vm.a' is responsive vm.b = 2 // 'vm.b' is non-responsiveCopy the code

Vue does not allow dynamic root-level responsive properties to be added to already created instances. However, you can add responsive properties to nested objects using the vue.set (Object, propertyName, value) method. For example, for:

Vue.set(vm.someObject, 'b', 2)
Copy the code

You can also use the vm.$set instance method, which is also an alias for the global vue.set method:

this.$set(this.someObject,'b',2)
Copy the code

Sometimes you may need to assign multiple new properties to existing objects, such as object.assign () or _.extend(). However, new properties that are thus added to the object do not trigger updates. In this case, you should create a new object with the property of the original object and the property of the object to be mixed in.

// Replace 'object.assign (this.someObject, {a: 1, b: 2})' this.someObject = object.assign ({}, this.someobject, {a: 1, b: 2}) 'this.someobject = object.assign ({}, this.someobject, {a: 1, b: 2}) 2})Copy the code

For an array of

Vue cannot detect changes to the following arrays:

  1. When you set an array item directly using an index, for example:vm.items[indexOfItem] = newValue
  2. When you modify the length of an array, for example:vm.items.length = newLength

Here’s an example:

Var vm = new Vue({data: {items: ['a', 'b', 'c']}}) vm.items[1] = 'x' // not responsive vm.items. Length = 2 // Not responsiveCopy the code

To solve the first type of problem, the following two methods can achieve the same effect as vm.items[indexOfItem] = newValue, and also trigger status updates within a responsive system:

// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
Copy the code
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
Copy the code

You can also use the vm.$set instance method, which is an alias for the global method vue. set:

vm.$set(vm.items, indexOfItem, newValue)
Copy the code

To solve the second type of problem, you can use Splice:

vm.items.splice(newLength)
Copy the code

Declare a responsive property

Since Vue does not allow you to dynamically add root-level responsive properties, you must declare all root-level responsive properties, even if they are null, before initializing the instance:

Var vm = new Vue({data: {// declare message as a null string message: ''}, template: '< div > {{message}} < / div >'}) / / after setting ` message ` vm. The message = 'Hello! 'Copy the code

If you do not declare message in the data option, Vue will warn you that the render function is trying to access property that does not exist.

There are technical reasons behind this restriction, which eliminates a class of boundary cases in dependency tracking systems and allows Vue instances to work better with type checking systems. At the same time, however, there is an important consideration for code maintainability: Data objects are like component state schemas. Declaring all of the reactive properties up front makes the component code easier to understand if it changes in the future or if it is read by other developers.

Asynchronous update queue

In case you haven’t noticed, Vue executes asynchronously when updating the DOM. As long as it listens for data changes, Vue opens a queue and buffers all data changes that occur in the same event loop. If the same watcher is triggered more than once, it will only be pushed into the queue once. This removal of duplicate data while buffering is important to avoid unnecessary computation and DOM manipulation. Then, in the next event loop, “TICK,” Vue refreshes the queue and performs the actual (de-duplicated) work. Vue internally attempts to use native Promise.then, MutationObserver, and setImmediate for asynchronous queues, and setTimeout(fn, 0) instead if the execution environment does not support it.

For example, when you set vm.someData = ‘new value’, the component does not immediately re-render. When the queue is refreshed, the component is updated in the next event loop “TICK”. In most cases we don’t need to worry about this process, but if you want to do something based on the updated DOM state, it can be tricky. While vue.js generally encourages developers to think in a “data-driven” way and avoid direct contact with the DOM, sometimes we have to. To wait for Vue to finish updating the DOM after the data changes, use vue.nexttick (callback) immediately after the data changes. This callback will be called after the DOM update is complete. Such as:

<div id="example">{{message}}</div>
Copy the code
var vm = new Vue({ el: '#example', data: { message: $el.textContent === 'new message' // false vue.nexttick (function () {$el.textContent === 'new Message '// false vue.nexttick (function () { vm.$el.textContent === 'new message' // true })Copy the code

Using the vm.$nextTick() instance method within a component is particularly convenient because it does not require a global Vue and this in the callback function is automatically bound to the current Vue instance:

Vue.component('example', { template: '<span>{{ message }}</span>', data: function () { return { message: }}, methods: {updateMessage: Function () {this.message = 'updated' console.log(this.$el.textContent) // => 'unupdated' this.$nextTick(function () {this.message = 'updated' console.log(this. The console. The log (enclosing $el. TextContent) / / = > 'updated'}}}})Copy the code

Because $nextTick() returns a Promise object, you can do the same thing using the new ES2017 async/await syntax:

methods: { updateMessage: Async function () {this.message = 'updated' console.log(this.$el.textContent) // => 'unupdated' await this.$nextTick() Console. log(this.$el.textContent) // => 'Updated'}}Copy the code