v-if遇到setTimeout(() => {emit('xxx')},0)

309 阅读2分钟

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已经不存在了。