详解Vue 数据更新了但页面没有更新的 7 种情况汇总及延伸总结


Posted in Javascript onMay 28, 2020

如果你发现你自己需要在 Vue 中做一次强制更新,99.9% 的情况,是你在某个地方做错了事。

1. Vue 无法检测实例被创建时不存在于 data 中的 property

原因:由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的。

场景:

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

解决办法:

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

2. Vue 无法检测对象 property 的添加或移除

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

场景:

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

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

解决办法:

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

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

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

3. Vue 不能检测通过数组索引直接修改一个数组项

原因:官方 - 由于 JavaScript 的限制,Vue 不能检测数组和对象的变化;尤雨溪 - 性能代价和获得用户体验不成正比。

场景:

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

解决办法:

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

拓展:Object.defineProperty() 可以监测数组的变化

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

场景:

var arr = [1, 2, 3, 4]
arr.forEach(function(item, index) {
  Object.defineProperty(arr, index, {
  set: function(value) {
   console.log('触发 setter')
   item = value
  },
  get: function() {
   console.log('触发 getter')
   return item
  }
 })
})
arr[1] = '123' // 触发 setter
arr[1]     // 触发 getter 返回值为 "123"
arr[5] = 5   // 不会触发 setter 和 getter

4. Vue 不能监测直接修改数组长度的变化

原因:官方 - 由于 JavaScript 的限制,Vue 不能检测数组和对象的变化;尤雨溪 - 性能代价和获得用户体验不成正比。

场景:

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

解决办法:

vm.items.splice(newLength)

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

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

场景:

<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' // 页面没有变化

解决办法:

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' // 文字颜色变成红色
})

拓展:异步更新带来的数据响应的误解

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

上段代码中,我们在 data 对象中声明了一个 message 空对象,然后在下次 DOM 更新循环结束之后触发的异步回调中,执行了如下两段代码:

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

到这里,模版更新了,页面最后会显示 我更新啦!

模板更新了,应该具有响应式特性,如果这么想那么你就已经走入了误区。

一开始我们在 data 对象中只是声明了一个 message 空对象,并不具有 text 属性,所以该 text 属性是不具有响应式特性的。

但模板切切实实已经更新了,这又是怎么回事呢?

那是因为 Vue.js 的 DOM 更新是异步的,即当 setter 操作发生后,指令并不会立马更新,指令的更新操作会有一个延迟,当指令更新真正执行的时候,此时 text 属性已经赋值,所以指令更新模板时得到的是新值。

模板中每个指令/数据绑定都有一个对应的 watcher 对象,在计算过程中它把属性记录为依赖。之后当依赖的 setter 被调用时,会触发 watcher 重新计算 ,也就会导致它的关联指令更新 DOM。

详解Vue 数据更新了但页面没有更新的 7 种情况汇总及延伸总结

具体流程如下所示:

  • 执行 this.dataObj = {}; 时, setter 被调用。
  • Vue.js 追踪到 message 依赖的 setter 被调用后,会触发 watcher 重新计算。
  • this.message.text = 'new text'; 对 text 属性进行赋值。
  • 异步回调逻辑执行结束之后,就会导致它的关联指令更新 DOM,指令更新开始执行。

所以真正的触发模版更新的操作是 this.message = {};这一句引起的,因为触发了 setter,所以单看上述例子,具有响应式特性的数据只有 message 这一层,它的动态添加的属性是不具备的。

对应上述第二点 - Vue 无法检测对象 property 的添加或移除

6. 循环嵌套层级太深,视图不更新?

看到网上有些人说数据更新的层级太深,导致数据不更新或者更新缓慢从而导致试图不更新?

由于我没有遇到过这种情况,在我试图重现这种场景的情况下,发现并没有上述情况的发生,所以对于这一点不进行过多描述(如果有人在真实场景下遇到这种情况留个言吧)。

针对上述情况有人给出的解决方案是使用强制更新:

如果你发现你自己需要在 Vue 中做一次强制更新,99.9% 的情况,是你在某个地方做错了事。

vm.$forceUpdate()

7. 拓展:路由参数变化时,页面不更新(数据不更新)

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

原因:路由视图组件引用了相同组件时,当路由参会变化时,会导致该组件无法更新,也就是我们常说中的页面无法更新的问题。

场景:

<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 是没有变化的。

解决办法:

解决的办法有很多种,这里只列举我常用到几种方法。

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

<router-view> 绑定 key 属性,这样 Vue 就会认为这是不同的 <router-view>

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

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

参考:

对 Vue 响应式数据更新的误解 - https://github.com/xiaofuzi/deep-in-vue/issues/11

[小丸子的城堡] - https://www.cnblogs.com/youhong/p/12173354.html

到此这篇关于详解Vue 数据更新了但页面没有更新的 7 种情况汇总及延伸总结的文章就介绍到这了,更多相关Vue 数据更新内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
javascript 原型继承介绍
Aug 30 Javascript
jQuery.extend 函数详解
Feb 03 Javascript
php与js的区别是什么
Aug 05 Javascript
JavaScript中字面量与函数的基本使用知识
Oct 20 Javascript
基于jquery实现左右按钮点击的图片切换效果
Jan 27 Javascript
JavaScript实现显示函数调用堆栈的方法
Apr 21 Javascript
JSON 对象未定义错误的解决方法
Sep 29 Javascript
react+ant design实现Table的增、删、改的示例代码
Dec 27 Javascript
详解Vue基于vue-quill-editor富文本编辑器使用心得
Jan 03 Javascript
详解webpack引入第三方库的方式以及注意事项
Jan 15 Javascript
vue使用nprogress实现进度条
Dec 09 Javascript
Vue登录拦截 登录后继续跳转指定页面的操作
Aug 04 Javascript
Vue实现附件上传功能
May 28 #Javascript
如何使用Javascript中的this关键字
May 28 #Javascript
简单了解JavaScript arguement原理及作用
May 28 #Javascript
如何使用JavaScript检测空闲的浏览器选项卡
May 28 #Javascript
js实现轮播图特效
May 28 #Javascript
JS写滑稽笑脸运动效果
May 28 #Javascript
Python版实现微信公众号扫码登陆
May 28 #Javascript
You might like
PHP动态分页函数,PHP开发分页必备啦
2011/11/07 PHP
YII模块实现绑定二级域名的方法
2014/07/09 PHP
php删除数组中重复元素的方法
2015/12/22 PHP
我也种棵OO树JXTree[js+css+xml]
2007/04/02 Javascript
jquery 学习笔记 传智博客佟老师附详细注释
2020/09/12 Javascript
JS如何判断移动端访问设备并解析对应CSS
2013/11/27 Javascript
javascript计算星座属相(十二生肖属相)示例代码
2014/01/09 Javascript
CSS3,HTML5和jQuery搜索框集锦
2014/12/02 Javascript
Sort()函数的多种用法
2016/03/20 Javascript
Vue常用指令V-model用法
2017/03/08 Javascript
AngularJS模糊查询功能实现代码(过滤内容下拉菜单排序过滤敏感字符验证判断后添加表格信息)
2017/10/24 Javascript
vue axios 在页面切换时中断请求方法 ajax
2018/03/05 Javascript
vue动态改变背景图片demo分享
2018/09/13 Javascript
对vue下点击事件传参和不传参的区别详解
2018/09/15 Javascript
JavaScript 如何在浏览器中使用摄像头
2020/12/02 Javascript
linux服务器快速卸载安装node环境(简单上手)
2021/02/22 Javascript
pymongo实现多结果进行多列排序的方法
2015/05/16 Python
python如何在循环引用中管理内存
2018/03/20 Python
django+mysql的使用示例
2018/11/23 Python
将python安装信息加入注册表的示例
2019/11/20 Python
Python爬虫定时计划任务的几种常见方法(推荐)
2021/01/15 Python
详解HTML5中ol标签的用法
2015/09/08 HTML / CSS
HTML5 Canvas入门学习教程
2016/03/17 HTML / CSS
AmazeUI 模态窗口的实现代码
2020/08/18 HTML / CSS
美国厨房和园艺工具网上商店:Nestneed
2019/08/24 全球购物
Under Armour安德玛意大利官网:美国高端运动科技品牌
2020/01/16 全球购物
什么是类的返射机制
2016/02/06 面试题
大学毕业通用个人的求职信
2013/12/08 职场文书
销售副总经理岗位职责
2013/12/11 职场文书
九年级物理教学反思
2014/01/29 职场文书
公司门卫的岗位职责
2014/02/19 职场文书
好员工观后感
2015/06/17 职场文书
python opencv常用图形绘制方法(线段、矩形、圆形、椭圆、文本)
2021/04/12 Python
Go使用协程交替打印字符
2021/04/29 Golang
java中用float时,数字后面加f,这样是为什么你知道吗
2021/09/04 Java/Android
Java Redisson多策略注解限流
2022/09/23 Java/Android