详解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 相关文章推荐
jquery $(this).attr $(this).val方法使用介绍
Oct 08 Javascript
JavaScript中prototype为对象添加属性的误区介绍
Oct 15 Javascript
JS辨别访问浏览器判断是android还是ios系统
Aug 19 Javascript
浅析Node.js查找字符串功能
Sep 03 Javascript
JavaScript 动态加载脚本和样式的方法
Apr 13 Javascript
JavaScript 事件对内存和性能的影响
Jan 22 Javascript
详解创建自定义的Angular Schematics
Jun 06 Javascript
关于AngularJS中ng-repeat不更新视图的解决方法
Sep 30 Javascript
详解Express笔记之动态渲染HTML(新手入坑)
Dec 13 Javascript
Vue 实现前进刷新后退不刷新的效果
Jun 14 Javascript
JavaScript 函数用法详解【函数定义、参数、绑定、作用域、闭包等】
May 12 Javascript
JSON获取属性值方法代码实例
Jun 30 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
基于mysql的论坛(2)
2006/10/09 PHP
PHP+APACHE实现用户论证的方法
2006/10/09 PHP
php定义数组和使用示例(php数组的定义方法)
2014/03/29 PHP
百度工程师讲PHP函数的实现原理及性能分析(一)
2015/05/13 PHP
PHP常用函数之格式化时间操作示例
2019/10/21 PHP
通过JAVASCRIPT读取ASP设定的COOKIE
2007/02/15 Javascript
jQuery 开天辟地入门篇一
2009/12/09 Javascript
html5 canvas js(数字时钟)实例代码
2013/12/23 Javascript
在JS中解析HTML字符串示例代码
2014/04/16 Javascript
JavaScript动态改变HTML页面元素例如添加或删除
2014/08/10 Javascript
Javascript保存网页为图片借助于html2canvas库实现
2014/09/05 Javascript
让angularjs支持浏览器自动填表
2014/11/10 Javascript
node.js中使用socket.io制作命名空间
2014/12/15 Javascript
Bootstrap每天必学之导航条(二)
2016/03/01 Javascript
JavaScript学习笔记之创建对象
2016/03/25 Javascript
JavaScript sort数组排序方法和自我实现排序方法小结
2016/06/06 Javascript
JavaScript的for循环中嵌套一个点击事件的问题解决
2017/03/03 Javascript
js实现3D图片展示效果
2017/03/09 Javascript
浅谈通过JS拦截 pushState和replaceState事件
2017/07/21 Javascript
vue-quill-editor富文本编辑器简单使用方法
2018/09/21 Javascript
简单说明Python中的装饰器的用法
2015/04/24 Python
基于Django filter中用contains和icontains的区别(详解)
2017/12/12 Python
Python通过调用有道翻译api实现翻译功能示例
2018/07/19 Python
python3 自动识别usb连接状态,即对usb重连的判断方法
2019/07/03 Python
python opencv 图像边框(填充)添加及图像混合的实现方法(末尾实现类似幻灯片渐变的效果)
2020/03/09 Python
在ipython notebook中使用argparse方式
2020/04/20 Python
python实现简单猜单词游戏
2020/12/24 Python
Python实现京东抢秒杀功能
2021/01/25 Python
介绍java中初始化块的使用
2012/09/11 面试题
《第一朵杏花》教学反思
2014/04/16 职场文书
计算机毕业大学生求职信
2014/06/26 职场文书
幸福家庭事迹材料
2014/12/20 职场文书
2015年教师自我评价范文
2015/03/04 职场文书
nginx配置proxy_pass中url末尾带/与不带/的区别详解
2021/03/31 Servers
ORACLE数据库应用开发的三十个注意事项
2021/06/07 Oracle
电脑只能进入安全模式无法正常启动的解决办法
2022/04/08 数码科技