Vue视图不更新问题初探

Posted 前端打字员

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue视图不更新问题初探相关的知识,希望对你有一定的参考价值。

我们在做Vue 项目的时候可能经常会遇到如下情况: 数据变化了,可视图没有更新。 本文将初步探索出现上述问题的原因及解决方法。
1
Vue更新机制
Vue的一大特色就是其非侵入性的响应式系统。当你将一个普通的javascript对象传入Vue的实例并作为data选项时,Vue就会遍历其所有属性,并通过Object.defineProperty [1] 将这些属性转为getter/setter。通过这些getter/setter,Vue就可以追踪到这些属性的访问与修改了。Vue的每一个组件实例都对应一个watcher实例,在组件渲染的过程中涉及到的数据属性都会被其记录为组件的依赖。当这些依赖的setter被触发时,watcher就会收到通知,进而更新与之相关的组件。
Vue视图不更新问题初探
【图1】数据变化与组件更新的关系
2
常见的问题及解决方法
在Vue项目中,有一些常见的坑是需要我们小心处理的。


对象属性的变更
由于现代JavaScript的限制,Vue无法检测到对象属性的添加与删除。 属性需要在Vue实例化的时候就在data中声明出来,否则是不响应的。
let vm = new Vue({  data() {      return {          a0,      };  },});// vm.a 是响应式的
vm.b = 1;// vm.b 是非响应的
在上述代码中,a属性是在Vue实例化时候就存在于data选项中的,所以a属性是响应式的。 在后续的应用中,如果在视图中使用了a属性,则当a变化时,视图会响应式地更新。
在上述代码中,虽然对b属性也做了声明,但是由于b属性在Vue组件实例化时并未存在于data选项中,b属性没有经过getter/setter转换,所以b属性不是响应式的。 在后续的应用中,如果在视图中使用了b属性,即便b属性发生了变化,视图也不会响应与更新。
由此可见,视图能否更新、某个属性是否是响应式的,其关键是让这个属性经过getter/setter转换。 我们可以通将所有需要用到的属性都明确地声明在data选择项中,来确保其可响应性。 又或者,我们实在是想在使用过程中添加一些属性的话也是有办法的:
Vue提供了Vue.set(object, propertyName, value)方法,向嵌套对象中添加响应式属性。
let vm = new Vue({ data() { return { author: { name: 'Apisit Lee', }, }; },});
Vue.set(vm.author, 'gender', 'male');
// vm.author.gender 是响应式的!
在上述代码中,Vue实例初始化时定义了author对象,包含一个name属性。通过set方法向author添加了gender属性,也是响应式的!这为我们提供了一种灵活的赋值方式,而且可以使用Vue的实例方法$set(Vue.set的别名):
this.$set(vm.author, 'gender''male');
有时需要添加多个新属性, 比如使用 Object.assign() 或 _.extend()。 这时需要小心,很有可能会出现视图不更新的情况。 在这种情况下,可以将原对象与新属性混合在一起创建一个新的对象。 比如,
// 这种方式不触发更新Object.assign(this.author, {age: 26});
// 这种方式会触发更新this.author = Object.assign({}, this.author, {age: 26});
以上说的是对于已有的响应式对象添加新属性,接下来要说一下往根对象(即data选项)中添加新属性的情况。
 Vue 不允许动态添加根级响应式属性,所以,所有要用到的根级别的响应式属性都必须要在Vue实例化之前定义好,哪怕只是个空值占着位置。 否则,Vue 将警告你渲染函数正在试图访问不存在的属性。


数组的变更

由于JavaScript限制,Vue无法监听以下两类数组变化:

  1.  通过索引直接改变数组项,例如:this.arrObject[itemIndex] = newVal;

  2.  修改数组的长度,例如:this.arrObject.length = newLength;


对于第一类问题,可以通过Vue提供的set方法(或$set实例方法)或者数组的splice方法来解决,比如:

Vue.set(this.arrObject, itemIndex, newVal);// 或者this.$set(this.arrObject, itemIndex, newVal);// 或者this.arrObject.splice(itemIndex, 1, newVal);

对于第二类问题,可以用数组的splice方法解决,比如:

this.arrObject.splice(newLength);


何时更新视图

Vue在更新DOM的时候是异步执行的。当Vue侦听到数据变化后,不是立即更新视图,而是开启一个队列,将同一事件循环中的所有数据变化都缓存在队列中。然后在下一事件循环中更新DOM。由于这种更新机制,在同一事件循环中,数据的频繁变化并不会引起视图的频繁变更,这一点对于避免不必要的计算与渲染是很重要的。


一般我们在使用Vue时,其“数据驱动”的思想可以让我们减少对DOM的直接操作,更专注于计算逻辑。但是,有时我们后续的操作依赖于DOM更新后的状态,这种情况下,单纯的数据驱动是很难满足需求的。所以,Vue又提供了一个方法Vue.nextTick(callback)用于即时获取到DOM更新后的状态。

<div id="example">{{message}}</div>
const vm = new Vue({ el: '#example', data: { message: '123' }});vm.message = 'new message'; // 更改数据vm.$el.textContent === 'new message'; // falseVue.nextTick(function () { vm.$el.textContent === 'new message'; // true});
如上代码所示,当数据message更新后,立即判断视图内的文本是否与新数据一致,结果是false。 在nextTick回调中判断,即可跳出当前事件循环,及时地获取视图更新后的状态,所以,此时的判断结果是true。

在组件内,我们可以使用组件方法vm.$nextTick(),而且回调中的this能自动绑定到当前组件实例上,用起来很方便。 另外,由于$nextTick()返回的是一个Promise对象,所以可以结合async/await来使用,比如:
methods: { updateMessage: async function () {        this.message = '已更新' console.log(this.$el.textContent) // => '未更新'      await this.$nextTick()      console.log(this.$el.textContent) // => '已更新'    }}

----------------------------------
[1] 由于Object.defineProperty在IE8及以下的浏览器中无法shim,致使Vue不支持这些浏览器


Vue视图不更新问题初探
以上是本打字员对于Vue视图不更新问题的初步探讨,如有高见,请直接发消息与我联系探讨~



以上是关于Vue视图不更新问题初探的主要内容,如果未能解决你的问题,请参考以下文章

回归 | js实用代码片段的封装与总结(持续更新中...)

vue中detele删除对象属性时视图不能响应更新 - 解决办法

不更新片段中的回收站视图

片段中的 notifyDataSetChanged() 不刷新列表视图

vue中数据更新视图不更新的问题

刷新片段不再起作用?