首页 » 前端 » 正文

Vue不能检测数据变动的原因和解决办法

熟悉vue的同学都有过这样的经历,操作数据的时候,如果“使用不当”可能让你的页面“无动于衷”。这也是大家经常会聊的问题,vue中的数据操作,没有更新视图,今天来细说一下这个点。

一、直接给data里塞入一个数据

需要在UI上显示的数据没有在data里进行声明,于是不显示。

原因:

vue无法检测实例被创建时,不存在于data中的property。由于vue会在初始化实例时,对property执行getter/setter转化,所以,property必须在data对象上存在才能让vue将它转化为响应式的。

解决办法:

秉承一个基本原则,需要在页面中渲染的数据先在data中添加默认值,其他逻辑数据挂载到this即可。

二、给 data 中的对象属性添加一个新的属性

data声明了一个对象数据,页面上修改这个对象的property。或者obj.b 已经成功添加,但是视图并未刷新。

原因:

vue无法检测对象property的添加或移除。官方–由于JavaScript(ES5)的限制,Vue.js不能检测到对象属性的添加或删除。因为vue.js在初始化实例时将属性转getter/setter,所以属性必须在data对象上才能让vue.js转换它,才能让它使响应的。

解决办法:

//动态添加 -- 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);

三、vue不能检测通过数组索引值,直接修改一个数组项

原因:

官方–由于JavaScript的限制,Vue不能检测数组和对象的变化;性能代价和获得用户体验不成正比。当然这个解释有点笼统,从性能方面考虑,假设数组中只有4个有意义的元素值,但是长度却是1000,我们想为1000个元素做检测操作。虽然说for循环中多余的空操作不会有太大性能消耗,但是出于低碳逻辑考虑,确实没那个必要。

Object.defineProperty()可以监测到数组的变化。但对数组新增一个属性(index)不会监测到数据变化,因为无法监测到新增数组的小标(index),删除一个属性(index)也是。

解决办法:

//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);

四、vue不能监测直接修改数组长度的变化

原因:

官方–由于JavaScript的限制,vue不能检测数组和对象的变化;(性能代价和获得用户体验不成正比)。

解决办法:

vm.items.splice(newLength)

常用到的数组操作会触发 Array 的响应式方法,如 pushpopshiftunshiftsplicesortreverse

五、在异步更新执行之前操作DOM数据不会变化

vue无法检测对象property的添加或移除 原因:

Vue在更新DOM时是异步执行的。只要侦听到数据变化,vue将开启一个队列,并缓冲在同一个事件循环中发生的所有数据变更。如果同一个watcher被多次触发,只会被推入到队列中一次。这种在缓冲时,去除重复数据对于避免不必要的计算和DOM操作是非常重要的。然后,在下一个的时间循环”tick”中,vue刷新队列并执行实际(已去重的)工作。vue在内部对异步队列尝试使用原生的Promise.then、MutationObserver和setImmediate,如果执行环境不支持,则会采用setTimeout(fn,0)代替。

解决办法:

vm.message = 'new message' // 更改数据
//使用Vue.nextTick(callback) callback将在DOM更新完成后被调用
Vue.nextTick(function(){
	vm.$el.textContent === 'new message'  //true
})

六、扩展:路由参数变化时,页面不更新(数据不更新)

原因: 扩展一个因为路由参数变化,而导致页面不更新的问题,页面不更新本质上就是数据没有更新。

<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
})

上段代码中,我们在路由构建选项routes中配置了一个动态路由’/home/:name’,他们共用一个路由组件Home,这代表他们复用RouterView。
当进行路由切换时,页面只会渲染第一次路由匹配到的参数,之后再进行路由切换时,message是没有变化的。

解决办法:

1、通过watch监听$route的变化:

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、给绑定key属性,这样vue就会认为这是不同的。

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

弊端:如果从/home跳转到/user等其他路由下,我们是不用担心组件更新问题的,所以这个时候key属性是多余的。

强制更新

如果有人真的要用那些容易出问题的方法,其实也有一些方法进行强制更新,但还是提醒各位按照正确的方法写。

1、用JSON.parse(JSON.stringify(objectOrArray))

通常是某个渲染的数组改变了层级较深的数据导致页面没有实时渲染。就这么写:this.items=JSON.parse(JSON.stringify(this.items));

2、用:key

给没有渲染改变数据的html元素加入:key=”update”,定义一个update:false,每次修改数据的时候在后面加一句this.update=!this.update;就可以刷新渲染了

3、用$set

这个在前面的方法中已经提到了

4、用 $forceUpdate

在修改数据之后加入this.$forceUpdate();即可

5、用 v-if

就是给需要刷新数据点html标签加上v-if,让其重新渲染(笨办法)

6、用location.replace(“”);(不推荐)

直接重新location.replace(“”); 刷新整个网页,不推荐都说轻了,直接禁用!

Vue3中不会出现这种问题

<template>
    <div class="">
      <div>{{ state.name }} - {{ state.age }}</div>
      <button @click="updateName">Change</button>
    </div>
</template>
 
<script>

import { reactive } from 'vue';
 
export default {
  setup() {
    const state = reactive({
        age: 30
      name: 'John Doe',
    });
    function updateName() {
      // 新增属性
      state.name = 'John';
    }

    // 返回响应式状态对象,可在模板中使用
    return { state,updateName };
  },
};
 </script>

它的这种自动化得益于在vue3中对数据响应式的优化:

1、Proxy替换Object.defineProperty

Vue 3.0使用了ES6中的Proxy代理对象来替代Object.defineProperty()方法。Proxy对象可以拦截对象上的一些操作,比如读取、修改、删除等,从而实现更加灵活的响应式更新。

2、更好的响应式追踪

在Vue 3.0中,响应式变量的追踪由Track和Trigger两个阶段完成。Track阶段用于追踪响应式变量的读取,Trigger阶段用于重新计算相关的渲染函数并更新视图。这种方式可以避免了无效渲染,提高了应用的性能。

3、更灵活的响应式更新

Vue 3.0还增加了watchEffect API和ref API等新API,可以更加灵活地对响应式变量进行操作。watchEffect可以监听响应式变量,并在变量变化时自动执行副作用函数。ref可以把普通变量转换成响应式变量,并返回一个带有value属性的对象。这些新API可以方便地扩展和操作响应式系统。