最近在vue项目中使用了一个复杂对象,发现改变该对象的属性值,页面视图未发生改变,查了一下,发现这个问题你们仍是有遇到过的,记录一下解决方案。vue
问题:vue数据结构有多层,改变二级结构数据,dom节点没有从新渲染?????ajax
数组
数据结构
1.DOM更新循环是指什么?app
2.下次更新循环是何时?框架
3.修改数据以后使用,是加快了数据更新进度吗?dom
4.在什么状况下要用到?异步
ide
在vue的文档中,说明vue是异步执行DOM更新的。函数
具体来讲,异步执行的运行机制以下。
全部同步任务都在主线程上执行,造成一个执行栈(execution context stack)。
主线程以外,还存在一个任务队列(task queue)。只要异步任务有了运行结果,就在“任务队列”之中放置一个时间。
一旦“执行栈”中的全部同步任务执行玩不,系统就会读取“任务队列”,看看里面有哪些事件。哪些对应的异步任务,因而结束等待状态,进入执行栈,开始执行。
主线程不断重复上面的第三步。
下图就是主线程和任务队列的示意图。
1 //改变数据 2 vm.message = 'changed' 3 //想要当即使用更新后的DOM,这样是不行的,由于设置的message后DOM尚未更新 4 console.log(vm.$el.textContent) //并无获得改变后的changed 5 //使用nextTick()这样就能够了,nextTick()里面的代码会再DOM更新后执行 6 Vue.nextTick(function(){ 7 console.log(vm.$el.textContent) //能够获得changed 8 })
第一个tick(图例中第一个步骤,即‘本次更新循环’):
首先修改数据,这是同步任务。同一事件循环的全部同步任务都在主线程上执行,造成一个执行栈,此时还未涉及到DOM
vue开启任务队列,并缓冲在此事件中繁盛的全部数据改变。若是同一个watcher被屡次触发,只会被推入到队列中一次
第二个tick(图例中第二个步骤,即‘下次更新循环’)
同步任务执行完毕,开始执行异步watcher队列中的任务,更新DOM。Vue在内部尝试对异步队列使用原生的Promise.then 和MessageChannel方法,若是执行环境不支持,会采用setTimeout(fn,0)代替。
第三个tick(图例中第三个步骤)
此时就是文档中所说的下次DOM更新循环结束以后
。
Vue.$nextTick()
获取的DOM更新后的数据 ,Vue.nextTick()
数据更新后的数据。
此时经过Vue.nextTick(获取到改变后的DOM).经过setTimeout(fn,0)也能够获取到。
简单总结事件循环:
同步代码执行-->查找异步队列,推入执行栈,执行Vue.nextTick[事件循环1] -->查找异步队列,推入执行栈,执行Vue.nextTick[事件循环2] ...
总之,异步是单独的一个tick,不会和同步在tick里发生,也是DOM不会立刻变化的缘由。
应用场景:须要在视图更新以后,基于新的视图进行操做
注意 mounted 不会承诺全部的子组件也都一块儿被挂载。若是你但愿等到整个视图都渲染完毕,能够用 vm.$nextTick 替换掉 mounted.
1 mounted: function () { 2 this.$nextTick(function () { 3 // Code that will run only after the 4 // entire view has been rendered 5 }) 6 }
eg1:
点击按钮显示本来以 v-show = false 隐藏起来的输入框,并获取焦点。
1 showsou(){ 2 this.showit = true //修改 v-show 3 document.getElementById("keywords").focus() //在第一个 tick 里,获取不到输入框,天然也获取不到焦点 4 }
修改成:
1 showsou(){ 2 this.showit = true 3 this.$nextTick(function () { 4 // DOM 更新了 5 document.getElementById("keywords").focus() 6 }) 7 }
点击获取元素宽度。
1 <div id="app"> 2 <p ref="myWidth" v-if="showMe">{{ message }}</p> 3 <button @click="getMyWidth">获取p元素宽度</button> 4 </div> 5 6 getMyWidth() { 7 this.showMe = true; 8 //this.message = this.$refs.myWidth.offsetWidth; 9 //报错 TypeError: this.$refs.myWidth is undefined 10 this.$nextTick(()=>{ 11 //dom元素更新后执行,此时能拿到p元素的属性 12 this.message = this.$refs.myWidth.offsetWidth; 13 }) 14 }
使用 swiper 插件经过 ajax 请求图片后的滑动问题。
$.each(data.resultData,function (index,item) { item.showChild = false; self.tableData.push(item) })
1 //this.$nextTick()不能监听到底层的数据变化 2 showDetail(item){ 3 console.log(this.tableData) 4 console.log(item) 5 this.$nextTick(function () { 6 item.showChild = !item.showChild 7 }) 8 },
因此在此用深度监听:
1 watch:{ 2 tableData:{ 3 handler:function(val,oldVal){ 4 this.tableData = val 5 }, 6 //深度监听 7 deep:true 8 } 9 }
1 // bad 2 created() { 3 this.fetchUserList(); 4 }, 5 watch: { 6 searchText: 'fetchUserList', 7 }
// good watch: { searchText: { handler: 'fetchUserList', immediate: true, } }
有时数据层次太多,视图没有自动更新,需手动强制刷新。添加this.$forceUpdate()
进行强制渲染,效果实现。