详解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 相关文章推荐
ajax异步刷新实现更新数据库
Dec 03 Javascript
JavaScript中json对象和string对象之间相互转化
Dec 26 Javascript
IE的事件传递-event.cancelBubble示例介绍
Jan 12 Javascript
js前端日历控件(悬浮、拖拽、自由变形)
Mar 02 Javascript
Bootstrap导航简单实现代码
Mar 06 Javascript
angular仿支付宝密码框输入效果
Mar 25 Javascript
Angular2使用Augury来调试Angular2程序
May 21 Javascript
vue2.0 常用的 UI 库实例讲解
Dec 12 Javascript
jQuery实现动态显示select下拉列表数据的方法
Feb 05 jQuery
理顺8个版本vue的区别(小结)
Sep 17 Javascript
微信小程序实现通过双向滑动缩放图片大小的方法
Dec 30 Javascript
判断JavaScript中的两个变量是否相等的操作符
Dec 21 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的access操作类
2008/04/09 PHP
php自动适应范围的分页代码
2008/08/05 PHP
php 友好URL的实现(吐血推荐)
2008/10/04 PHP
解析thinkphp中的M()与D()方法的区别
2013/06/22 PHP
PHP和MySql中32位和64位的整形范围是多少
2016/02/18 PHP
关于PHP通用返回值设置方法
2017/03/31 PHP
在Node.js中实现文件复制的方法和实例
2014/06/05 Javascript
jQuery实现文本框邮箱输入自动补全效果
2015/11/17 Javascript
Jquery插件之Fancybox丰富的弹出层效果附源码下载
2015/12/02 Javascript
深入浅析react native es6语法
2015/12/09 Javascript
jquery操作select元素和option的实例代码
2016/02/03 Javascript
使用jQuery判断Div是否在可视区域的方法 判断div是否可见
2016/02/17 Javascript
Bootstrap三种表单布局的使用方法
2016/06/21 Javascript
mpvue小程序仿qq左滑置顶删除组件
2018/08/03 Javascript
vue滚动固定顶部及修改样式的实例代码
2019/05/30 Javascript
Python中MYSQLdb出现乱码的解决方法
2014/10/11 Python
500行Python代码打造刷脸考勤系统
2019/06/03 Python
python如何实现视频转代码视频
2019/06/17 Python
Django项目创建到启动详解(最全最详细)
2019/09/07 Python
Python 实现文件读写、坐标寻址、查找替换功能
2019/09/11 Python
使用Python的datetime库处理时间(RPA流程)
2019/11/24 Python
pytorch 状态字典:state_dict使用详解
2020/01/17 Python
Win 10下Anaconda虚拟环境的教程
2020/05/18 Python
python3实现语音转文字(语音识别)和文字转语音(语音合成)
2020/10/14 Python
基于Python组装jmx并调用JMeter实现压力测试
2020/11/03 Python
使用css3做0.5px的细线的示例代码
2018/01/18 HTML / CSS
心碎乌托邦的创业计划书范文
2013/12/26 职场文书
11月升旗仪式讲话稿
2014/02/15 职场文书
应届本科毕业生求职信
2014/07/23 职场文书
党员目标管理责任书
2014/07/25 职场文书
三八红旗手主要事迹材料
2015/11/04 职场文书
领导干部学习心得体会
2016/01/23 职场文书
python3.9之你应该知道的新特性详解
2021/04/29 Python
react国际化react-intl的使用
2021/05/06 Javascript
正确使用MySQL update语句
2021/05/26 MySQL
日本十大血腥动漫,那些被禁播的动漫盘点
2022/03/21 日漫