Go deep into the vUE responsive principle
This is my third article in the newbie campaign
Non-invasive responsive systems
We know that the VUE website says that VUE is a non-invasive responsive system, so how do we understand this invasive and non-invasive?
In general, programmers write code using frameworks that are either intrusive or non-intrusive. Intrusive is the need for user code to inherit from classes provided by the framework. Non-invasive does not require user code to introduce information about the framework code, and from the class writer’s point of view, the framework does not exist.
The non-invasive nature of Vue makes it easy for our code to migrate without too many dependencies.
How to track change
We all know that vUE’s responsive principle uses object.defineProperty to hijack data, but what is the specific process?
When you pass a normal JavaScript object to a Vue instance as the data option, Vue will iterate through all the properties of that object, Use object.defineProperty to turn all of these properties into getters/setters. Object.defineproperty is an ES5 feature that cannot shim (introduce a new API into an old environment and implement it only by means of existing methods in the old environment), 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 to get a more user-friendly user interface for examining data.
Each component instance has a watcher instance that records properties touched as dependencies during component rendering. The watcher is then notified when the setter for the dependency fires, causing its associated component to be rerendered.
Matters needing attention
Due to JavaScript limitations, Vue cannot detect changes to arrays and objects. Still, there are ways to circumvent these limitations and keep them responsive.
object
Vue cannot detect the addition or removal of a property. Since Vue performs getter/setter transformations on the property when initializing the instance, the property must exist on the data object for Vue to convert it to responsive. Such as:
var vm = new Vue({
data: {a:1}})// 'vm.a' is reactive
vm.b = 2
// 'vm.b' is non-responsive
Copy the code
Vue does not allow dynamic addition of root-level responsive properties for an already created instance. However, you can add reactive properties to nested objects using the vue.set (object, propertyName, value) method. For example, for:
Vue.set(vm.someObject, 'b'.2)
Copy the code
Sometimes you may want to assign multiple new properties to an existing Object, such as using object.assign () or _.extend(). However, a new property added to the object thus does not trigger an update. In this case, you should create a new object with the property of the original object and the property of the object you want to mix in.
Assign (this.someObject, {a: 1, b: 2}) '
this.someObject = Object.assign({}, this.someObject, { a: 1.b: 2 })
Copy the code
An array of
Vue cannot detect changes to the following arrays:
- When you set an array item directly using an index, for example: vm.items[indexOfItem] = newValue
- When you change 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 responsive
Copy the code
To solve the first type of problem, both of the following methods can achieve the same effect as vm.items[indexOfItem] = newValue, while also triggering 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 dynamically adding root-responsive properties, you must declare all root-responsive properties, even a null value, before initializing the instance:
var vm = new Vue({
data: {
// Declare message as a null string
message: ' '
},
template: '<div>{{ message }}</div>'
})
// Then set 'message'
vm.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 a property that does not exist.
There is a technical reason behind this limitation. It eliminates a class of boundary cases in dependency tracking systems and makes Vue instances work better with type checking systems. But there is also an important consideration in terms of code maintainability: Data objects are like a schema for component state. Declaring all responsive properties in advance makes the component code easier to understand when it is modified in the future or given to other developers to read.
Asynchronous update queue
In case you haven’t noticed, Vue executes asynchronously when updating the DOM. As soon as data changes are listened for, Vue opens a queue and buffers all data changes that occur in the same event loop. If the same watcher is fired multiple times, it will only be pushed into the queue once. This removal of duplicate data at buffering time is important to avoid unnecessary computation and DOM manipulation. Then, in the next event loop “tick”, Vue refreshes the queue and performs the actual (deduplicated) work. Vue internally tries to use native Promise. Then, MutationObserver, and setImmediate for asynchronous queues, and if the execution environment does not support it, setTimeout(fn, 0) is used instead.
For example, when you set vm.someData = ‘new value’, the component does not immediately rerender. When the queue is flushed, the component is updated in the next event loop “tick”. Most of the time we don’t need to care about this process, but if you want to do something based on the updated DOM state, it can get a little tricky. While vue.js generally encourages developers to think “data-driven” and avoid direct contact with the DOM, sometimes it is necessary. To wait for Vue to finish updating the DOM after the data change, use vue.nexttick (callback) immediately after the data change. The callback function will then be called after the DOM update is complete. Such as:
<div id="example">{{message}}</div>
var vm = new Vue({
el: '#example'.data: {
message: '123'
}
})
vm.message = 'new message' // Change the data
vm.$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 global Vue, and this in the callback function automatically binds 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'}}}})Copy the code
Since $nextTick() returns a Promise object, you can use the new ES2017 async/await syntax to do the same thing:
methods: {
updateMessage: async function () {
this.message = 'Updated'
console.log(this.$el.textContent) // => 'not updated'
await this.$nextTick()
console.log(this.$el.textContent) // => 'updated'}}Copy the code
Interested students can also go to look at the vUE source code, here do not expand