我今天提了个要求,场景是这样的:
在页面上拉一个接口,该接口返回该页面的浮动层组件所依赖的一些数据。然后接口一返回数据,我就显示这个浮动层组件,同时向后台报告一些数据(这些数据是父组件从接口获取的)。这时,一件神奇的事情发生了。虽然我得到了数据,但在显示浮动图层时,它没有更新到组件。
父组件:
模板.pop ref=' pop ' : name=' name '/templatescriptexportdefault {.已创建(){ 0.//请求数据,获取数据数据.从接口获取({ URL 3360xxxx,success 3360(data)={//问题出现在这里。赋值后,直接调用show方法来显示。调用show方法时,会报告数据,但报告的数据尚未更新到子组件this。名称=数据。命名这个。$ refs . pop . show()} })} }/脚本子组件
模板div-show=' IsShow './div/templatescriptexport默认值{.prop :[' name '],Methods: {show () {this。isshow=true//report ('XXX ',{name:this。名称})} }/脚本问题分析:
原因分析在vue官网(cn.vuejs.org/v2/guide/re.)
如果您没有注意到,Vue异步执行DOM更新。只要观察到数据变化,Vue就会打开一个队列,并在同一事件周期内缓冲所有数据变化。如果同一观察器被触发多次,它将只被推入队列一次。这种在缓冲过程中删除重复数据的方法对于避免不必要的计算和DOM操作非常重要。然后,在下一个事件循环“滴答”中,Vue刷新队列并执行实际的(已消除重复数据的)工作。Vue试图在内部为异步队列使用本机Promise.then和MessageChannel。如果执行环境不支持,将使用setTimeout(fn,0)。
也就是说,当我们在父组件中设置this.name=name时,vue不会直接更新到子组件中(dom update也不会立即执行),而是将所有这些更新操作放入一个队列中,同一组件的所有这些赋值操作都将作为一个观察器的更新操作放入这个队列中,然后当事件循环结束时,将一次从这个队列中获取所有wath cher来执行更新操作。在我们的例子中,当我们调用show时,实际上,我们的this.name=name并没有真正执行,而是被放入队列中。这种vue的方法无疑是基于优化的,否则,如果在有N个赋值vue的情况下进行N次dom更新,效率将会非常低,而且不理想。
下面的更新操作是指更新数据值的操作,数据将被放入vue中异步执行的队列中。
解决方案:
1.使用nextTick延迟show方法的执行(一般来说,在数据真正更新后,执行所有需要更新的操作)
通过上面的分析,我们知道我们对vue实例的所有更新操作都会先放入队列,异步执行会被延迟。这些异步操作要么是microtask,要么是macrotask(无论是microtask还是macroktask都取决于环境,这反映在nextTick的源代码中)。根据事件循环机制,首先执行第一个队列,所以如果我们在nextTick中执行操作,就会变成这样。
2.使用setTimeout来延迟show方法的执行。原理同上
因此,我们的解决方案可以是:
这个。name=data . namesettimeout(()={ this。$ refs.pop.show ()})或
这个的实现原理。名称=数据。说出这个。$ nexttick (()={this。$ refs.pop.show ()}) nexttick
实际上,nextTick的实现原理相当简单。简单来说就是异步,通过不同的执行环境以不同的方式实现,保证了nextTick中的回调函数可以异步执行。你为什么要这么做?因为vue异步更新dom。
在下面发布源代码:
/** *将任务推迟到异步执行*/export const next tick=(function(){ const回调=[]let pending=false let timer func func next tichandler(){ pending=false const copy=回调。切片(0)回调。长度=0为(设I=0;一。份数。长度;I){ copy[I]()} }//nextTick行为利用了微任务队列,可以通过本机答应我。然后或突变服务器/访问该队列//突变服务器有更广泛的支持,然而在iOS=9.3.3中的//UIWebView中,当在触摸事件处理程序中触发时,它被严重窃听。它//触发几次后完全停止工作.因此,如果原生//承诺可用,我们将使用它: /*伊斯坦布尔忽略如果*/如果(承诺类型!=='未定义'是动词(承诺)){风险值p=承诺。resolve()var日志错误=err={ console。错误(错误)}计时器函数=()={ p .然后(下一个刻度处理程序).catch(logError) //在有问题的UIWebViews中答应我,然后不会完全中断,但是//它会陷入一种奇怪的状态,回调被推入//微任务队列,但是队列没有被刷新,直到浏览器//需要做一些其他的工作,例如处理一个计时器。因此,我们可以//"强制"通过添加一个空计时器来刷新微任务队列if(isIOS)setTimeout(noop)} } else if(!国际工业生态学会类型的突变服务器!==' undefined '(是原生的(MutationObserver)| |//PhantomJS和iOS 7。x突变服务器。tostring()=='[object mutationobserverconconstructor]'){//在原生承诺不可用的地方使用突变服务器,//例如Phantomjs、iOS 7、Android 4.4 var计数器=1 var observer=new MutationObserver(next tick处理程序)var textNode=document。createtextnode(字符串(计数器))观察者。观察者。观察者(文本节点,文本节点,{字符:功能ctx?对象){ let _resolve回调。推(()={ if(CB){尝试{ CB。call(ctx)} catch(e){ handleError(e,CTX,' next tick ')} } else if(_ resolve){ _ resolve(CTX)} })if(!pending){ pending=true timerFunc()} if(!可换股债券类型的承诺!=='未定义'){ 0返回新的承诺(解析,拒绝)={ _resolve=resolve }) })()首先我们看到这个是利用了闭包的特性,返回queueNextTick,所以我们实际调用的下一步其实就是调用queueNextTick,一调用这个方法,就会把下一步的回调放入队列回调当中,等到合适的时机,会将回调中的所有回调取出来执行,以达到延迟执行的目的。为啥要用闭包呢,我觉得有两个原因:
1、共享变量,比如回调、待定和timerFunc。
2、避免反复判断,即是避免反复判断timerFunc是利用承诺还是利用突变观察器或是定时器来实现异步,这是函数柯里化的一种运用。
这里有两个最主要的方法需要解释下:
1、下一个TickHandler
这个函数,就是把队列中的回调,全部取出来执行,类似于微任务的任务队列。我们通过调用Vue .$nextTick就会把回调全部放入这个队列当中,等到要执行的时候,调用nextTickHandler全部取出来执行。
2、timerFunc
这个变量,它的作用就是通过承诺/变更观察者/设定输出把nextTickHandler放入到真正的任务队列当中,等到事件循环结束,就从任务队列当中取出nextTickHandler来执行,下一个TickHandler一执行,回调里面的所有回调就会被取出来执行来,这样就达到来延迟执行下一步传的回调的效果。
通过这个简单的源码分析,我们可以得出两个结论
1.根据不同的执行环境,异步任务可以是microtask或macrotask,而不是固定的。因此,如果你想让nextTick中的所有异步任务都被视为microtask,你会遇到一个坑。
2.nextTick不保证你能得到更新后的dom,这取决于你是先分配数据还是先调用nextTick。例如:
new Vue({ el: '#app ',data() { return { id: 2 } },created() { },Mounted () {this。$ next tick(()={ console . log(document . getelementbyid(' id ')。textcontent)//2打印在这里,因为nexttick被称为first}) this。id=3}})结论
如果想要获取更新后的DOM或子组件(取决于父组件传递的值),可以在更新操作后立即使用Vue.nextTick(回调),注意这里的顺序,先更新操作,然后调用nextTick获取更新后的DOM/子组件。在源代码中,我们知道nextTick不能保证它能得到更新后的DOM/子组件。
以上是边肖介绍的nextTick在vue中的使用,希望对大家有所帮助。如果你有任何问题,请给我留言,边肖会及时回复你。非常感谢您对我们网站的支持!