v-if遇到setTimeout(() => {emit('xxx')},0)
一个例子
当我们需要在父子组件间通信时候,我们需要使用emit
子组件
setup(props,{emit}){ function handleClick(){ emit('XXX') } }这时当子组件中触发了点击事件,调用handleClick函数之后,将会发送自定义事件XXX到父组件
父组件
<SubComponent v-if="isVisblie" @XXX="onSubClick" v-click-outside="handleClickOutside"> </SubComponent> ... function handleClickOutside(){ this.isVisblie = false } function onSubClick(){ console.log('ok') }父组件会调用onSubClick方法,打印出 ok
当这个例子中子组件使用了setTimeout
子组件
setup(props,{emit}){ function handleClick(){ setTiemout(() => {emit(XXX)},0) } }这时将emit放入setTimeout中,将会在下轮事件循环中emit出这个自定义事件XXX
父组件
<SubComponent v-if="isVisblie" @XXX="onSubClick" v-click-outside="handleClickOutside"> </SubComponent> ... function handleClickOutside(){ this.isVisblie = false } function onSubClick(){ console.log('ok') }由于存在v-click-outside,而子组件中的点击事件会出发clickoutside,所以在本轮事件循环结束之前,SubComponent已经被销毁了,所以自定义事件XXX的处理函数也就无法执行
为什么不加setTimeout就可以正常执行
首先我们回顾一下Vue的异步更新队列
Vue在观察到数据变化时并不会直接更新DOM,而是会开启一个队列,并将同一事件循环中发生的所有数据改变放入这个队列
为什么需要这个队列
Vue会在同一事件循环中不断维护这个队列,以此去除重复的改变,避免进行不必要的DOM操作和计算。
等到本轮事件循环末尾,或下一轮事件循环开启的时候(这取决于Vue采用哪种方法,Vue优先采用Promise.then,但是如果浏览器不支持,Vue会采用setTimeout)
异步更新队列如何解释上面的错误
- 当没有在子组件中加入setTimeout的时候,emit方法将在本轮事件循环中执行,这时,虽然触发emit的点击事件虽然已经将isVisblie更改为false,但是Vue会将销毁SubComponent放入异步更新队列,所以当emit被触发的时候SubComponent还没有被销毁。
- 当在子组件中加入setTimeout的时候,emit方法将在作为宏任务被放入宏任务队列,将在下一次的事件循环开始时被主线程取出放入执行栈执行,而这时异步更新队列中的销毁SubComponent的操作已经被执行,所以在emit方法被调用的时候,SubComponent已经不存在了。