数据改变,视图未变解决
最近在vue项目中使用了一个复杂对象,发现改变该对象的属性值,页面视图未发生改变,查了一下,发现这个问题大家还是有遇到过的,记录一下解决方案。
问题:vue数据结构有多层,改变二级结构数据,dom节点没有重新渲染?????
1.DOM更新循环是指什么?
2.下次更新循环是什么时候?
3.修改数据之后使用,是加快了数据更新进度吗?
4.在什么情况下要用到?
在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 }
修改为:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 showsou(){ 2 this.showit = true 3 this.$nextTick(function () { 4 // DOM 更新了 5 document.getElementById("keywords").focus() 6 }) 7 }
点击获取元素宽度。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 请求图片后的滑动问题。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
$.each(data.resultData,function (index,item) { item.showChild = false; self.tableData.push(item) })
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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()
进行强制渲染,效果实现。