事件队列
想了解vue中的事件队列,要先懂宏任务和微任务的区别。
https://zhuanlan.zhihu.com/p/42117531
在响应式、编译和渲染的过程中,都没有触发过微任务。我们在组件渲染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()
}
}
}
如果不排序导致的后果:
正在探究....