事件队列

想了解vue中的事件队列,要先懂宏任务和微任务的区别。

https://zhuanlan.zhihu.com/p/42117531

img

在响应式、编译和渲染的过程中,都没有触发过微任务。我们在组件渲染instance.update中,创建了effect,并且传递了options,这个options是告诉响应式数据触发trigger的时候,使用options.scheduler(effect)方法,而不会直接effect()。

trigger中触发effect的方法run:

const run = (effect: ReactiveEffect) => {
    if (__DEV__ && effect.options.onTrigger) {
      effect.options.onTrigger({
        effect,
        target,
        key,
        type,
        newValue,
        oldValue,
        oldTarget
      })
    }
    if (effect.options.scheduler) { // 渲染processComponent的时候在setupRenderEffect方法中,instance.update = effect(()=>{}, prodEffectOptions)
      effect.options.scheduler(effect)
    } else {
      effect()
    }
  }

options:

const prodEffectOptions = {
    scheduler: queueJob
}
// 当effect被trigger的时候,会执行queueJob(effect)

queue相关:

// queue
const queue: (Job | null)[] = []

// queueJob
function queueJob(job: Job) {
  if (!queue.includes(job)) {
    queue.push(job)
    queueFlush()
  }
}

function queueFlush() {
  if (!isFlushing && !isFlushPending) {
    isFlushPending = true
    nextTick(flushJobs)
  }
}

function flushJobs(seen?: CountMap) {
  debugger
  isFlushPending = false
  isFlushing = true
  let job
  if (__DEV__) {
    seen = seen || new Map()
  }

  // Sort queue before flush.
  // This ensures that:
  // 1. Components are updated from parent to child. (because parent is always
  //    created before the child so its render effect will have smaller
  //    priority number)
  // 2. If a component is unmounted during a parent component's update,
  //    its update can be skipped.
  // Jobs can never be null before flush starts, since they are only invalidated
  // during execution of another flushed job. 从小到大执行 从父组件子执行 反正当前Effect只影响当前的 问题不大吧 props也不是
  queue.sort((a, b) => getId(a!) - getId(b!)) // 小到大 如果为null 则为Infinate effect的id 从小到大执行 因为effect 嵌套 是大到小被track收集的 BFS就是如此

  while ((job = queue.shift()) !== undefined) {
    if (job === null) {
      continue
    }
    if (__DEV__) {
      checkRecursiveUpdates(seen!, job)
    }
    callWithErrorHandling(job, null, ErrorCodes.SCHEDULER)
  }
  flushPostFlushCbs(seen)
  isFlushing = false
  // some postFlushCb queued jobs!
  // keep flushing until it drains.
  if (queue.length || postFlushCbs.length) { // 免得在postFlushCbs内部生成 所以再次flushJobs 但是seen会保留
    flushJobs(seen) // 传承Map
  }
}

nextTick:

const p = Promise.resolve()

function nextTick(fn?: () => void): Promise<void> { // 卧槽promise 微任务... 服了 就这么简单
  return fn ? P.then(fn) : p
}

我们有任务A,如果队列queue中没有任务A,则queue.push(A),使用queueFlush方法,标记队列准备执行,把flushJobs丢进微任务中。

微任务启动,执行flushJobs,标记队列正在执行,对queue中的任务,根据任务id,从小到大进行排序,为什么要从小到大进行排序呢

先说一下组件的更新:

假设我们有父组件A,子组件B,响应式数据R,A与B的effect均被R追踪到,追踪到的顺序是先A的A-effect,后B的B-effect,R变动,使A-effect和B-effect相继进入queue,queueFlush被推进微任务,执行A的A-update,

A-update创建子组件B的新的B-vnode(只是创建),进行patch,patch检测到旧的B-vnode和新的B-vnode不一致,需要进行更新,删除queue中的B-effect,设置旧B-instance.next为新B-vnode,调用B-effect,此时queue中effect的排列将为先A-effect后B-effect,完成A-update的钩子。

接着执行B-effect,检测到旧B.instance.next,调用updateComponentPreRender,去更新旧的B-instance为新的B-instance,更新B的子vnode(假设B的子vnode为普通Element),完成B-update的钩子。

整个更新就完成了。

组件的移除:

假设有响应式数据Z,A的A-effect被Z追踪到,当Z.value为true,A组件的子组件为B,如果为false,A组件的子组件将为C。

现在把Z.value设置为false,触发A-effect,创建新C-vnode,patch(B-vnode, C-vnode),对比发现两个vnode不相等,使用unmount卸载B-vnode。

// unmount
const unmount: UnmountFn = (
    vnode, // B-vnode
    parentComponent, // 当前例子是传入A-instance
    parentSuspense, // suspense组件的特性,当前并没有涉及
    doRemove = false // true
  ) => {
    const {
      type,
      props,
      ref,
      children,
      dynamicChildren,
      shapeFlag,
      patchFlag,
      dirs
    } = vnode
    const shouldInvokeDirs = shapeFlag & ShapeFlags.ELEMENT && dirs
    const shouldKeepAlive = shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
    let vnodeHook: VNodeHook | undefined | null

    // 去除ref 想了解ref去ref章节
    if (ref != null && parentComponent) {
      setRef(ref, null, parentComponent, null)
    }

    // keepAlive 就不执行props.beforeUnMount钩子
    if ((vnodeHook = props && props.onVnodeBeforeUnmount) && !shouldKeepAlive) {
      invokeVNodeHook(vnodeHook, parentComponent, vnode)
    }

    if (shapeFlag & ShapeFlags.COMPONENT) { // 组件相关
      if (shouldKeepAlive) {
        ;(parentComponent!.ctx as KeepAliveContext).deactivate(vnode)
      } else { // 移除组件
        unmountComponent(vnode.component!, parentSuspense, doRemove)
      }
    }
  }

// unmountComponet
const unmountComponent = (
    instance: ComponentInternalInstance,
    parentSuspense: SuspenseBoundary | null,
    doRemove?: boolean
  ) => {
    if (__DEV__ && instance.type.__hmrId) {
      unregisterHMR(instance)
    }

    const { bum, effects, update, subTree, um, da, isDeactivated } = instance
    // beforeUnmount hook
    if (bum) {
      invokeArrayFns(bum)
    }
    if (effects) { // 停止被追踪
      for (let i = 0; i < effects.length; i++) {
        stop(effects[i]) // 可以手动用stop停止某个行为的追踪
      }
    }
    // update may be null if a component is unmounted before its async
    // setup has resolved.
    if (update) {
      stop(update) // updtae 是 effect
      unmount(subTree, instance, parentSuspense, doRemove) // 卸载子组件/其他
    }
    // unmounted hook 钩子 unmounted
    if (um) {
      queuePostRenderEffect(um, parentSuspense)
    }
    // deactivated hook 指令
    if (
      da &&
      !isDeactivated &&
      instance.vnode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
    ) {
      queuePostRenderEffect(da, parentSuspense)
    }
    queuePostRenderEffect(() => {
      instance.isUnmounted = true
    }, parentSuspense)

    // A component with async dep inside a pending suspense is unmounted before
    // its async dep resolves. This should remove the dep from the suspense, and
    // cause the suspense to resolve immediately if that was the last dep.
    if (
      __FEATURE_SUSPENSE__ &&
      parentSuspense &&
      !parentSuspense.isResolved &&
      !parentSuspense.isUnmounted &&
      instance.asyncDep &&
      !instance.asyncResolved
    ) {
      parentSuspense.deps--
      if (parentSuspense.deps === 0) {
        parentSuspense.resolve()
      }
    }
  }

如果不排序导致的后果:

正在探究....

results matching ""

    No results matching ""