数据改变,视图未变解决

最近在vue项目中使用了一个复杂对象,发现改变该对象的属性值,页面视图未发生改变,查了一下,发现这个问题大家还是有遇到过的,记录一下解决方案。

问题:vue数据结构有多层,改变二级结构数据,dom节点没有重新渲染?????

1.$nextTick()

这个方法的意思就是数据更新后触发dom,官方文档说明:在下次DOM更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法 ,获取更新后的DOM。

疑问:

1.DOM更新循环是指什么?

2.下次更新循环是什么时候?

3.修改数据之后使用,是加快了数据更新进度吗?

4.在什么情况下要用到?

1.1原理

vue实现响应式并不是数据发生变化之后DOM立即变化,而是按一定的策略进行DOM的更新

在vue的文档中,说明vue是异步执行DOM更新的。

具体来说,异步执行的运行机制如下。

  • 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。

  • 主线程之外,还存在一个任务队列(task queue)。只要异步任务有了运行结果,就在“任务队列”之中放置一个时间。

  • 一旦“执行栈”中的所有同步任务执行玩不,系统就会读取“任务队列”,看看里面有哪些事件。哪些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。

  • 主线程不断重复上面的第三步。

下图就是主线程和任务队列的示意图。

 

 

 

1.2 事件循环说明

简单来说,VUE在数据变化后,视图不会立即更新,而是等同一事件中的所有数据发生变化之后,再统一进行视图更新。

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不会马上变化的原因。

1.3 用途

应用场景:需要在视图更新之后,基于新的视图进行操作

需要注意的是,在 created 和 mounted 阶段,如果需要操作渲染后的试图,也要使用 nextTick 方法。

注意 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 }

1.4 其他应用场景

其他应用场景如下三例:

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 }
View Code

eg2:

点击获取元素宽度。

 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 }
View Code

eg3:

使用 swiper 插件通过 ajax 请求图片后的滑动问题。

2.watch 监听

$nextTick这个方法的意思就是数据更新后触发dom节点更新,但是数据多层的时候vue监听不到底层的数据变化,可以使用watch方法深度监听数据的变化,然后使用$nextTick在数据变化后触发dom节点更新,并且数据获取到后要遍历数据放进定义的数组里不然也不会出发dom节点更新。

$.each(data.resultData,function (index,item) {
        item.showChild = false;
         self.tableData.push(item)
     })
View Code
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  },
View Code

所以在此用深度监听:

1 watch:{
2   tableData:{
3     handler:function(val,oldVal){
4       this.tableData = val
5     },
6       //深度监听
7       deep:true
8   }
9 }

watch 还有属性deep。当设置为true时,它会进行深度监听。简而言之就是你有一个 const obj={a:1,b:2},里面任意一个 key 的 value 发生变化的时候都会触发watch。应用场景:比如我有一个列表,它有一堆query筛选项,这时候你就能deep watch它,只有任何一个筛序项改变的时候,就自动请求新的数据。或者你可以deep watch一个 form 表单,当任何一个字段内容发生变化的时候,你就帮它做自动保存等等。

2.1 小技巧与建议

Watch immediate

这个已经算是一个比较常见的技巧了,这里就简单说一下。当 watch 一个变量的时候,初始化时并不会执行,如下面的例子,你需要在created的时候手动调用一次。

1 // bad
2 created() {
3   this.fetchUserList();
4 },
5 watch: {
6   searchText: 'fetchUserList',
7 }

你可以添加immediate属性,这样初始化的时候也会触发,然后上面的代码就能简化为:

// good
watch: {
  searchText: {
    handler: 'fetchUserList',
    immediate: true,
  }
}

3 $forceUpdate()

在使用Vue框架开发时,在函数中改变了页面中的某个值,在函数中查看是修改成功了,尝试了上面两种方式后,但在页面中没有及时刷新改变后的值。

有时数据层次太多,视图没有自动更新,需手动强制刷新。添加this.$forceUpdate()进行强制渲染,效果实现。

 

posted @ 2019-06-14 09:49  ichthyo-plu  阅读(1163)  评论(0编辑  收藏  举报