(Detailed explanation of error conditions and solutions) Vue data is updated but the page is not updated

Click to follow, like Shuan Q

background

In the vue project, some of us will encounter the situation that the data is modified, but the view is not updated. The specific scenarios are different, and the methods to solve the problem are also different. I have read a lot of articles on the Internet, and I will summarize them here. In view of the fact that the data update view is not updated, it is recommended to have a deeper understanding of the responsive principle of Vue.

If, you find yourself needing to do a forced update in vue, 99.9% of the time, you're doing something wrong somewhere.

 1. Summary of 7 situations where vue data is updated but the page is not updated

1. Vue cannot detect the property that does not exist in data when the instance is created

Reason: Since vue will perform getter/setter transformation on property when initializing the instance, property must exist on the data object in order for vue to convert it into a responsive one.

 ①Scene:

var vm = new Vue({
  data:{},
  // 页面不会变化
  template: '<div>{
   
   {message}}</div>'
})
vm.message = 'Hello!' // `vm.message` 不是响应式的

②Solution:

var vm = new Vue({
  data: {
    // 声明 a、b 为一个空值字符串
    message: '',
  },
  template: '<div>{
   
   { message }}</div>'
})
vm.message = 'Hello!'

2. Vue cannot detect the addition or removal of object properties

Reason: Official – Due to JavaScript (ES5) limitations, Vue.js cannot detect the addition or removal of object properties. Because vue.js converts properties into getters/setters when initializing an instance, the properties must be on the data object in order for vue.js to convert it and make it responsive.

 ①Scene:

var vm = new Vue({
  data:{
    obj: {
      id: 001
    }
  },
  // 页面不会变化
  template: '<div>{
   
   { obj.message }}</div>'
})

vm.obj.message = 'hello' // 不是响应式的
delete vm.obj.id       // 不是响应式的

②Solution:

//动态添加 -- vue.set
Vue.set(vm.obj,propertyName,newValue);

//动态添加 -- vm.$set
vm.$set(vm.obj,propertyName,newValue);

//动态添加多个
// 代替Object.assign(this.obj,{a:1,b:2})
this.obj = Object.assign({},this.obj,{a:1,b:2});

//动态移除--vm.$delect
Vue.delete(vm.obj,propertyName);

//动态移除 --vm.$delete
vm.$delete(vm.obj,propertyName);

3. Vue cannot detect through the array index value and directly modify an array item

Reason: Official – due to JavaScript limitations, Vue cannot detect changes in arrays and objects; the performance cost is not directly proportional to the user experience.

 ①Scene:

var vm = new Vue({
  data: {
    items: ['a', 'b', 'c']
  }
})
vm.items[1] = 'x' // 不是响应性的

②Solution:

//Vue.set
Vue.set(vm.items,indexOfItem,newValue);

//vm.$set
vm.$set(vm.items,indexOfItem,newValue);

//Array.prototype.splice
vm.items.splice(indexOfItem,1,newValue);

Extension: Object.defineProperty() can monitor array changes

Object.defineProperty() can monitor the changes of the array. However, adding an attribute (index) to an array will not detect data changes, because the subscript (index) of the newly added array cannot be monitored, and the same is true for deleting an attribute (index).

①Scene:

var arr = [1, 2, 3, 4]
arr.forEach(function(item, index) {     Object.defineProperty(arr, index, {     set: function(value) {       console.log('trigger setter')       item = value     },     get: function() {       console.log('trigger getter')       return item     }   }) }) arr[1] = '123' // trigger setter arr[1] // trigger getter return value is "123 " arr[5] = 5 // will not trigger setter and getter













 4. Vue cannot monitor the change of directly modifying the length of the array

Reason: Official – due to JavaScript limitations, vue cannot detect changes in arrays and objects; (the performance cost is not directly proportional to the user experience).

 ①Scene:

var vm = new Vue({
  data: {
    items: ['a', 'b', 'c']
  }
})
vm.items.length = 2 // 不是响应性的

②Solution:

vm.items.splice(newLength)

5. The operation DOM data will not change before the asynchronous update is executed

Reason: Vue is executed asynchronously when updating the DOM. As long as data changes are detected, Vue will open a queue and buffer all data changes that occur in the same event loop. If the same watcher is triggered multiple times, it will only be pushed into the queue once. This deduplication during buffering is very important to avoid unnecessary calculations and DOM manipulations. Then, on the next time loop "tick", Vue flushes the queue and performs the actual (deduplicated) work. Vue internally tries to use native Promise.then, MutationObserver and setImmediate for asynchronous queues. If the execution environment does not support it, it will use setTimeout(fn,0) instead.

 ①Scene:

<div id="example">{
   
   {message}}</div>
var vm = new Vue({
  el: '#example',
  data: {
    message: '123'
  }
})
vm.message = 'new message' // 更改数据
vm.$el.textContent === 'new message' // false
vm.$el.style.color = 'red' // 页面没有变化

 ②Solution:

var vm = new Vue({
  el: '#example',
  data: {
    message: '123'
  }
})
vm.message = 'new message' // 更改数据
//使用Vue.nextTick(callback) callback将在DOM更新完成后被调用
Vue.nextTick(function(){
	vm.$el.textContent === 'new message'  //true
	vm.$el.style.color = 'red' //文字颜色变成红色
})

Extension: misunderstanding of data response caused by asynchronous update

<!-- 页面显示:我更新啦! -->
<div id="example">{
   
   {message.text}}</div>
var vm = new Vue({
  el: '#example',
  data: {
    message: {},
  }
})
vm.$nextTick(function () {
  this.message = {}
  this.message.text = '我更新啦!'
})

In the above code, we declared an empty message object in the data object, and then executed the following two pieces of code in the asynchronous callback triggered after the next DOM update cycle ends:

this.message = {};
this.message.text = '我更新啦!'

At this point, the template has been updated, and the page will finally show that I have updated it! .
The template has been updated and should be responsive, if you think so then you have entered the wrong place.
At the beginning, we just declared an empty message object in the data object, which does not have a text attribute, so the text attribute is not responsive.
But the template has actually been updated, so what's going on?
That's because the DOM update of Vue.js is asynchronous, that is, when the setter operation occurs, the instruction will not be updated immediately, and there will be a delay in the update operation of the instruction. When the instruction update is executed, the text attribute has been assigned at this time. So when the directive updates the template, it gets the new value.

Every directive/databinding in the template has a corresponding watcher object, which records properties as dependencies during evaluation. Later, when the dependent setter is called, it will trigger the watcher to recalculate, which will also cause its associated instructions to update the DOM.

 

 The specific process is as follows:

  • When executing this.message = { };, the setter is called;
  • After Vue.js traces that the setter that the message depends on is called, it will trigger the watcher to recalculate;
  • this.message.text = 'I updated it! '; Assign a value to the text attribute;
  • After the execution of the asynchronous callback logic is completed, it will cause its associated instructions to update the DOM, and the instruction update will start to execute.
  • Therefore, the real operation that triggers the template update is this.message = { }; caused by this sentence, because the setter is triggered, so just looking at the above example, the only data with responsive characteristics is the message layer, which is dynamically added attribute is not available.

Corresponding to the second point above – Vue cannot detect the addition or removal of object properties

 6. The loop nesting level is too deep, the view is not updated?

I saw some people on the Internet saying that the level of data update is too deep, resulting in the data not being updated or the update being slow, resulting in the view not being updated?
PS: This is what I encountered, and I didn’t find any problems (embarrassing.jpg) for the time being, so I used the method of forced update.
In response to the above situation, someone gave a solution to use forced update :

If, you find yourself needing to do a forced update in vue, 99.99% of the time, you are doing something wrong somewhere.

vm.$forceUpdate()

7. Extension: When the routing parameters change, the page will not be updated (data will not be updated)

Expand a problem that the page is not updated due to changes in routing parameters. The fact that the page is not updated is essentially that the data is not updated.

Reason: When the routing view component refers to the same component, when the routing parameters change, the component cannot be updated, which is what we often say that the page cannot be updated.

①Scene:

<div id="app">
  <ul>
    <li><router-link to="/home/foo">To Foo</router-link></li>    
    <li><router-link to="/home/baz">To Baz</router-link></li>    
    <li><router-link to="/home/bar">To Bar</router-link></li>    
  </ul>    
  <router-view></router-view>
</div>
const Home = {
	template:`<div>{
   
   {message}}</div>`,
	data(){
		return{
			message:this.$route.params.name
		}
	}
}
const router = new VueRouter({
	mode:'history',
	  routes:[
	  {path:'/home',component:Home},
	  {path:'/home/:name',component:Home}
	  ]
})
new Vue({
	el:'#app',
	router
})

In the above code, we configured a dynamic route '/home/:name' in the route construction option routes. They share a route component Home, which means they reuse RouterView.
When the route is switched, the page will only render the parameters matched by the first route, and the message will not change when the route is switched later.

②Scene:

<div id="app">
  <ul>
    <li><router-link to="/home/foo">To Foo</router-link></li>    
    <li><router-link to="/home/baz">To Baz</router-link></li>    
    <li><router-link to="/home/bar">To Bar</router-link></li>    
  </ul>    
  <router-view></router-view>
</div>
const Home = {
  template: `<div>{
   
   {message}}</div>`,
  data() {
    return {
      message: this.$route.params.name
    }
  }
}

const router = new VueRouter({
  mode:'history',
    routes: [
    {path: '/home', component: Home },
    {path: '/home/:name', component: Home }
  ]
})

new Vue({
  el: '#app',
  router
})

In the above code, we configured a dynamic route '/home:/name' in the routing construction option routes, and they share a routing component Home, which means they reuse RouterView.
When the route is switched, the page will only render the parameters matched by the first route, and the message will not change when the route is switched later.

③Solutions:

There are many solutions, and here are just a few of the methods I commonly use.

1. Monitor $route changes through watch:

const Home = {
  template: `<div>{
   
   {message}}</div>`,
  data() {
    return {
      message: this.$route.params.name
    }
  },
  watch:{
	 '$route':function(){
		this.message = this.$route.params.name
	 }
  }
}
...
new Vue({
  el: '#app',
  router
})

2. Bind the key attribute, so that vue will think it is different.

Disadvantages: If you jump from /home to /user and other routes, we don't have to worry about component updates, so the key attribute is redundant at this time.

<div id="app">
  ...
  <router-view :key="key"></router-view>
</div>

Second, the method I often use:

  1. Bind the key value to dom, then update the data, and then change the key value, which can solve the problem
  2. force refresh
  3. set, delete, Object.assign() and other methods
  4. this.$nextTick(() => {

                    })

Guess you like

Origin blog.csdn.net/qq_59747594/article/details/128216340