constmountStaticNode = ( n2: VNode, container: RendererElement, anchor: RendererNode | null, isSVG: boolean ) => { // static nodes are only present when used with compiler-dom/runtime-dom // which guarantees presence of hostInsertStaticContent. [n2.el, n2.anchor] = hostInsertStaticContent!( n2.childrenasstring, container, anchor, isSVG, n2.el, n2.anchor ); };
/** * Dev / HMR only */ constpatchStaticNode = ( n1: VNode, n2: VNode, container: RendererElement, isSVG: boolean ) => { // static nodes are only patched during dev for HMR if (n2.children !== n1.children) { const anchor = hostNextSibling(n1.anchor!);
let { patchFlag, dynamicChildren, slotScopeIds: fragmentSlotScopeIds } = n2;
// 开发环境热更新时,强制全量diff if ( __DEV__ && // #5523 dev root fragment may inherit directives (isHmrUpdating || patchFlag & PatchFlags.DEV_ROOT_FRAGMENT) ) { // HMR updated / Dev root fragment (w/ comments), force full diff patchFlag = 0; optimized = false; dynamicChildren = null; }
// 检查是否是插槽 // check if this is a slot fragment with :slotted scope ids if (fragmentSlotScopeIds) { slotScopeIds = slotScopeIds ? slotScopeIds.concat(fragmentSlotScopeIds) : fragmentSlotScopeIds; }
// 当旧的片段为空时,挂载新的片段的子节点 if (n1 == null) { hostInsert(fragmentStartAnchor, container, anchor); hostInsert(fragmentEndAnchor, container, anchor); // a fragment can only have array children // since they are either generated by the compiler, or implicitly created // from arrays. mountChildren( n2.childrenasVNodeArrayChildren, container, fragmentEndAnchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized ); } else { // 当旧片段不为空时,启用优化则使用patchBlockChildren if ( patchFlag > 0 && patchFlag & PatchFlags.STABLE_FRAGMENT && dynamicChildren && // #2715 the previous fragment could've been a BAILed one as a result // of renderSlot() with no valid children n1.dynamicChildren ) { // a stable fragment (template root or <template v-for>) doesn't need to // patch children order, but it may contain dynamicChildren. patchBlockChildren( n1.dynamicChildren, dynamicChildren, container, parentComponent, parentSuspense, isSVG, slotScopeIds ); // 开发环境,热更新 处理静态子节点 if (__DEV__ && parentComponent && parentComponent.type.__hmrId) { traverseStaticChildren(n1, n2); } elseif ( // #2080 if the stable fragment has a key, it's a <template v-for> that may // get moved around. Make sure all root level vnodes inherit el. // #2134 or if it's a component root, it may also get moved around // as the component is being moved. n2.key != null || (parentComponent && n2 === parentComponent.subTree) ) { traverseStaticChildren(n1, n2, true/* shallow */); } } else { // 不可优化时,使用patchChildren处理 // keyed / unkeyed, or manual fragments. // for keyed & unkeyed, since they are compiler generated from v-for, // each child is guaranteed to be a block so the fragment will never // have dynamicChildren. patchChildren( n1, n2, container, fragmentEndAnchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized ); } } };
// The fast path for blocks. constpatchBlockChildren: PatchBlockChildrenFn = ( oldChildren, newChildren, fallbackContainer, parentComponent, parentSuspense, isSVG, slotScopeIds ) => { for (let i = 0; i < newChildren.length; i++) { const oldVNode = oldChildren[i]; const newVNode = newChildren[i]; // Determine the container (parent element) for the patch. const container = // oldVNode may be an errored async setup() component inside Suspense // which will not have a mounted element oldVNode.el && // - In the case of a Fragment, we need to provide the actual parent // of the Fragment itself so it can move its children. (oldVNode.type === Fragment || // - In the case of different nodes, there is going to be a replacement // which also requires the correct parent container !isSameVNodeType(oldVNode, newVNode) || // - In the case of a component, it could contain anything. oldVNode.shapeFlag & (ShapeFlags.COMPONENT | ShapeFlags.TELEPORT)) ? hostParentNode(oldVNode.el)! : // In other cases, the parent container is not actually used so we // just pass the block element here to avoid a DOM parentNode call. fallbackContainer; patch( oldVNode, newVNode, container, null, parentComponent, parentSuspense, isSVG, slotScopeIds, true ); } };
// 走绿色通道:用patchFlag来保证children是数组 // fast path if (patchFlag > 0) { if (patchFlag & PatchFlags.KEYED_FRAGMENT) { // 有key属性的时候,根据key来进行diff // this could be either fully-keyed or mixed (some keyed some not) // presence of patchFlag means children are guaranteed to be arrays patchKeyedChildren( c1 asVNode[], c2 asVNodeArrayChildren, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized ); return; } elseif (patchFlag & PatchFlags.UNKEYED_FRAGMENT) { // 没有key // unkeyed patchUnkeyedChildren( c1 asVNode[], c2 asVNodeArrayChildren, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized ); return; } }
// 没有patchFlag的保证,则children可能为文本、数组或空 // 根据形状标识来判断 // children has 3 possibilities: text, array or no children. if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { // 文本子节点的绿色通道 // text children fast path if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) { unmountChildren(c1 asVNode[], parentComponent, parentSuspense); } if (c2 !== c1) { hostSetElementText(container, c2 asstring); } } else { // 旧的子节点是数组 if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) { // prev children was array if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { // 新旧子节点都是数组,需要进行全量diff // two arrays, cannot assume anything, do full diff patchKeyedChildren( c1 asVNode[], c2 asVNodeArrayChildren, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized ); } else { // 新的子节点为空,则只需要卸载旧的子节点 // no new children, just unmount old unmountChildren(c1 asVNode[], parentComponent, parentSuspense, true); } } else { // 旧的子节点为文本节点或者空,新的为数组或空 // prev children was text OR null // new children is array OR null if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) { // 旧的为文本节点,先将其文本置空 hostSetElementText(container, ""); } // 新的为数组,则通过mountChildren挂载子节点 // mount new if array if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { mountChildren( c2 asVNodeArrayChildren, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized ); } } } };
// can be all-keyed or mixed constpatchKeyedChildren = ( c1: VNode[], c2: VNodeArrayChildren, container: RendererElement, parentAnchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, isSVG: boolean, slotScopeIds: string[] | null, optimized: boolean ) => { let i = 0; const l2 = c2.length; // 两组各自的尾节点 let e1 = c1.length - 1; // prev ending index let e2 = l2 - 1; // next ending index
// (从前往后) // 以两组中最短的一组为基准 // 从头结点开始,依次比较同一位置的节点类型,若头节点类型相同,则对两个节点进行patch进行比较; // 若类型不同则退出循环 // 1. sync from start // (a b) c // (a b) d e while (i <= e1 && i <= e2) { const n1 = c1[i]; const n2 = (c2[i] = optimized ? cloneIfMounted(c2[i] asVNode) : normalizeVNode(c2[i])); if (isSameVNodeType(n1, n2)) { patch( n1, n2, container, null, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized ); } else { break; } // i++; }
// (从后往前) // 从尾节点开始,尾节点类型相同,则通过patch比较尾节点; // 若类型不同则退出循环 // 2. sync from end // a (b c) // d e (b c) while (i <= e1 && i <= e2) { const n1 = c1[e1]; const n2 = (c2[e2] = optimized ? cloneIfMounted(c2[e2] asVNode) : normalizeVNode(c2[e2])); if (isSameVNodeType(n1, n2)) { patch( n1, n2, container, null, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized ); } else { break; } e1--; e2--; }
// 经过前后两轮比较之后,剩下的就是中间那部分类型不同的子节点了
// 若旧的子节点组已经遍历完,而新的子节点组还有剩余内容 // 通过patch处理剩下的新的子节点中的内容,由于旧的子节点为空, // 因此相当于在patch内部挂载剩余的新的子节点 // 3. common sequence + mount // (a b) // (a b) c // i = 2, e1 = 1, e2 = 2 // (a b) // c (a b) // i = 0, e1 = -1, e2 = 0 if (i > e1) { if (i <= e2) { const nextPos = e2 + 1; const anchor = nextPos < l2 ? (c2[nextPos] asVNode).el : parentAnchor; while (i <= e2) { patch( null, (c2[i] = optimized ? cloneIfMounted(c2[i] asVNode) : normalizeVNode(c2[i])), container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized ); i++; } } }
// 旧的子节点还有剩余内容而新的子节点组已经遍历完,则卸载旧子节点组剩余的那部分 // 4. common sequence + unmount // (a b) c // (a b) // i = 2, e1 = 2, e2 = 1 // a (b c) // (b c) // i = 0, e1 = 0, e2 = -1 elseif (i > e2) { while (i <= e1) { unmount(c1[i], parentComponent, parentSuspense, true); i++; } }
// 新旧子节点组都没有遍历完,如下注释中[]里的部分 // 5. unknown sequence // [i ... e1 + 1]: a b [c d e] f g // [i ... e2 + 1]: a b [e d c h] f g // i = 2, e1 = 4, e2 = 5 else { // 拿到上次比较完的起点 const s1 = i; // prev starting index const s2 = i; // next starting index
// 5.1 build key:index map for newChildren constkeyToNewIndexMap: Map<string | number | symbol, number> = newMap(); // 用Map存储新的子节点组的key和对应的index, key=>index 并给出重复的key的警告 for (i = s2; i <= e2; i++) { const nextChild = (c2[i] = optimized ? cloneIfMounted(c2[i] asVNode) : normalizeVNode(c2[i])); if (nextChild.key != null) { if (__DEV__ && keyToNewIndexMap.has(nextChild.key)) { warn( `Duplicate keys found during update:`, JSON.stringify(nextChild.key), `Make sure keys are unique.` ); } keyToNewIndexMap.set(nextChild.key, i); } }
// 5.2 loop through old children left to be patched and try to patch // matching nodes & remove nodes that are no longer present let j; // 已比较的数量 let patched = 0; // 未比较的数量 const toBePatched = e2 - s2 + 1; let moved = false; // used to track whether any node has moved let maxNewIndexSoFar = 0; // works as Map<newIndex, oldIndex> // Note that oldIndex is offset by +1 // and oldIndex = 0 is a special value indicating the new node has // no corresponding old node. // used for determining longest stable subsequence // 以新的子节点组中未完成比较的节点为基准 const newIndexToOldIndexMap = newArray(toBePatched); // 先用0来填充,标记为没有key的节点。 ps:直接fill(0)不就好了么 for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;
// 处理旧的子节点组 for (i = s1; i <= e1; i++) { const prevChild = c1[i]; // 当已经比较完了(patched >= toBePatched),卸载旧的子节点 if (patched >= toBePatched) { // all new children have been patched so this can only be a removal unmount(prevChild, parentComponent, parentSuspense, true); continue; } let newIndex; // 当旧的子节点的key存在,取出key在新的子节点组中对应的index if (prevChild.key != null) { newIndex = keyToNewIndexMap.get(prevChild.key); } else { // 若旧的子节点没有key,找出没有key且类型相同的节点对应在新子节点组中的index // key-less node, try to locate a key-less node of the same type for (j = s2; j <= e2; j++) { if ( newIndexToOldIndexMap[j - s2] === 0 && isSameVNodeType(prevChild, c2[j] asVNode) ) { newIndex = j; break; } } } // newIndex不存在,即根据key来找,发现旧的子节点不可复用,则卸载旧的子节点 if (newIndex === undefined) { unmount(prevChild, parentComponent, parentSuspense, true); } else { // 找到了可复用的节点,在newIndexToOldIndexMap中标记 i+1, // 用于最大上升子序列算法 newIndexToOldIndexMap[newIndex - s2] = i + 1; // 刷新目前找到的最大的新子节点的index,做节点移动标记 if (newIndex >= maxNewIndexSoFar) { maxNewIndexSoFar = newIndex; } else { moved = true; } // 再递归详细比较两个节点 patch( prevChild, c2[newIndex] asVNode, container, null, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized ); // 已对比的数量+1 patched++; } }
// 当需要移动时,采用最大递增子序列算法,从而最大限度减少节点移动次数 // 5.3 move and mount // generate longest stable subsequence only when nodes have moved const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : EMPTY_ARR; j = increasingNewIndexSequence.length - 1; // 倒序遍历,好处是可以使用上一次对比的节点作为锚点 // looping backwards so that we can use last patched node as anchor for (i = toBePatched - 1; i >= 0; i--) { const nextIndex = s2 + i; const nextChild = c2[nextIndex] asVNode; const anchor = nextIndex + 1 < l2 ? (c2[nextIndex + 1] asVNode).el : parentAnchor; if (newIndexToOldIndexMap[i] === 0) { // 等于0说明未被旧的子节点匹配到,属于全新的不可复用的子节点,则通过patch进行挂载 // mount new patch( null, nextChild, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized ); } elseif (moved) { // 当计算出来的最大上升子序列为空数组, // 或者当前节点不处于最大上升子序列中 // move if: // There is no stable subsequence (e.g. a reverse) // OR current node is not among the stable sequence if (j < 0 || i !== increasingNewIndexSequence[j]) { move(nextChild, container, anchor, MoveType.REORDER); } else { j--; } } } } };
constmountElement = ( vnode: VNode, container: RendererElement, anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, isSVG: boolean, slotScopeIds: string[] | null, optimized: boolean ) => { letel: RendererElement; letvnodeHook: VNodeHook | undefined | null; const { type, props, shapeFlag, transition, patchFlag, dirs } = vnode; if ( !__DEV__ && vnode.el && hostCloneNode !== undefined && patchFlag === PatchFlags.HOISTED ) { // vnode的el元素存在,仅在生产环境下对可复用的静态节点进行复制 // If a vnode has non-null el, it means it's being reused. // Only static vnodes can be reused, so its mounted DOM nodes should be // exactly the same, and we can simply do a clone here. // only do this in production since cloned trees cannot be HMR updated. el = vnode.el = hostCloneNode(vnode.el); } else { // vnode上的元素不存在则新建 el = vnode.el = hostCreateElement( vnode.typeasstring, isSVG, props && props.is, props );
// 注释:由于某些props依赖于子节点的渲染,先挂载子节点 // mount children first, since some props may rely on child content // being already rendered, e.g. `<select value>` if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { // 设置元素文本 hostSetElementText(el, vnode.childrenasstring); } elseif (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { // 挂载子节点 mountChildren( vnode.childrenasVNodeArrayChildren, el, null, parentComponent, parentSuspense, isSVG && type !== "foreignObject", slotScopeIds, optimized ); }
// 指令的created阶段 if (dirs) { invokeDirectiveHook(vnode, null, parentComponent, "created"); }
// 处理元素的props // props if (props) { for (const key in props) { if (key !== "value" && !isReservedProp(key)) { hostPatchProp( el, key, null, props[key], isSVG, vnode.childrenasVNode[], parentComponent, parentSuspense, unmountChildren ); } } /** * Special case for setting value on DOM elements: * - it can be order-sensitive (e.g. should be set *after* min/max, #2325, #4024) * - it needs to be forced (#1471) * #2353 proposes adding another renderer option to configure this, but * the properties affects are so finite it is worth special casing it * here to reduce the complexity. (Special casing it also should not * affect non-DOM renderers) */ if ("value"in props) { hostPatchProp(el, "value", null, props.value); } if ((vnodeHook = props.onVnodeBeforeMount)) { invokeVNodeHook(vnodeHook, parentComponent, vnode); } } // scopeId setScopeId(el, vnode, vnode.scopeId, slotScopeIds, parentComponent); } // __DEV__环境下处理 __vnode属性和父组件为不可枚举 if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) { Object.defineProperty(el, "__vnode", { value: vnode, enumerable: false, }); Object.defineProperty(el, "__vueParentComponent", { value: parentComponent, enumerable: false, }); } // 执行指令中的 beforeMount 阶段 if (dirs) { invokeDirectiveHook(vnode, null, parentComponent, "beforeMount"); } // #1583 For inside suspense + suspense not resolved case, enter hook should call when suspense resolved // #1689 For inside suspense + suspense resolved case, just call it
// 注释:patchFlag 标识的存在意味着元素的 render 代码是由 compiler 生成的, // 且可以在 patch 时走快道,此时能保证新旧节点形状相同,即它们在源模板中正好处于相同的位置 // 此时的对比是有着各种优化的 if (patchFlag > 0) { // the presence of a patchFlag means this element's render code was // generated by the compiler and can take the fast path. // in this path old node and new node are guaranteed to have the same shape // (i.e. at the exact same position in the source template) if (patchFlag & PatchFlags.FULL_PROPS) { // 当props中含有动态的key,需要进行全量 diff // element props contain dynamic keys, full diff needed patchProps( el, n2, oldProps, newProps, parentComponent, parentSuspense, isSVG ); } else { // 处理动态类名绑定 // class // this flag is matched when the element has dynamic class bindings. if (patchFlag & PatchFlags.CLASS) { if (oldProps.class !== newProps.class) { hostPatchProp(el, "class", null, newProps.class, isSVG); } }
// 处理动态的 style 绑定 // style // this flag is matched when the element has dynamic style bindings if (patchFlag & PatchFlags.STYLE) { hostPatchProp(el, "style", oldProps.style, newProps.style, isSVG); }
// 处理动态的 prop/attr 绑定,有迭代缓存,优化比较速度 // 如果 `prop/attr`的 key 是动态的,那么这种优化则会失效 // props // This flag is matched when the element has dynamic prop/attr bindings // other than class and style. The keys of dynamic prop/attrs are saved for // faster iteration. // Note dynamic keys like :[foo]="bar" will cause this optimization to // bail out and go through a full diff because we need to unset the old key if (patchFlag & PatchFlags.PROPS) { // if the flag is present then dynamicProps must be non-null const propsToUpdate = n2.dynamicProps!; for (let i = 0; i < propsToUpdate.length; i++) { const key = propsToUpdate[i]; const prev = oldProps[key]; const next = newProps[key]; // value属性会被强行对比 // #1471 force patch value if (next !== prev || key === "value") { hostPatchProp( el, key, prev, next, isSVG, n1.childrenasVNode[], parentComponent, parentSuspense, unmountChildren ); } } } }
// 处理文本:仅在元素只有文本子节点时触发 // text // This flag is matched when the element has only dynamic text children. if (patchFlag & PatchFlags.TEXT) { if (n1.children !== n2.children) { hostSetElementText(el, n2.childrenasstring); } } } elseif (!optimized && dynamicChildren == null) { // 没有优化,全量 diff // unoptimized, full diff patchProps( el, n2, oldProps, newProps, parentComponent, parentSuspense, isSVG ); }
// 处理props和插槽 // resolve props and slots for setup context if (!(__COMPAT__ && compatMountInstance)) { // 检测初始化性能 if (__DEV__) { startMeasure(instance, `init`); } // 处理setup:这个函数里使用其它方法,初始化了props和插槽,且调用了setup setupComponent(instance); if (__DEV__) { endMeasure(instance, `init`); } }
// 处理异步的setup // setup() is async. This component relies on async logic to be resolved // before proceeding if (__FEATURE_SUSPENSE__ && instance.asyncDep) { parentSuspense && parentSuspense.registerDep(instance, setupRenderEffect);
// Give it a placeholder if this is not hydration // TODO handle self-defined fallback if (!initialVNode.el) { const placeholder = (instance.subTree = createVNode(Comment)); processCommentNode(null, placeholder, container!, anchor); } return; }
constupdateComponent = (n1: VNode, n2: VNode, optimized: boolean) => { const instance = (n2.component = n1.component)!; if (shouldUpdateComponent(n1, n2, optimized)) { if (__FEATURE_SUSPENSE__ && instance.asyncDep && !instance.asyncResolved) { // async & still pending - just update props and slots // since the component's reactive effect for render isn't set-up yet if (__DEV__) { pushWarningContext(n2); } // 更新组件的预渲染:即处理props和插槽 updateComponentPreRender(instance, n2, optimized); if (__DEV__) { popWarningContext(); } return; } else { // normal update instance.next = n2; // in case the child component is also queued, remove it to avoid // double updating the same child component in the same flush. invalidateJob(instance.update); // instance.update is the reactive effect. instance.update(); } } else { // no update needed. just copy over properties n2.el = n1.el; instance.vnode = n2; } };
pauseTracking(); // props update may have triggered pre-flush watchers. // flush them before the render update. flushPreFlushCbs(undefined, instance.update); resetTracking(); };
if (__DEV__) { startMeasure(instance, `patch`); } // 更新则比较新旧subTree patch( prevTree, nextTree, // parent may have changed if it's in a teleport hostParentNode(prevTree.el!)!, // anchor may have changed if it's in a fragment getNextHostNode(prevTree), instance, parentSuspense, isSVG ); if (__DEV__) { endMeasure(instance, `patch`); } next.el = nextTree.el; if (originNext === null) { // self-triggered update. In case of HOC, update parent component // vnode el. HOC is indicated by parent instance's subTree pointing // to child component's vnode updateHOCHostEl(instance, nextTree.el); } // 处理updated钩子 // updated hook if (u) { queuePostRenderEffect(u, parentSuspense); } // onVnodeUpdated if ((vnodeHook = next.props && next.props.onVnodeUpdated)) { queuePostRenderEffect( () =>invokeVNodeHook(vnodeHook!, parent, next!, vnode), parentSuspense ); } if ( __COMPAT__ && isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance) ) { queuePostRenderEffect( () => instance.emit("hook:updated"), parentSuspense ); }
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) { devtoolsComponentUpdated(instance); }
if (__DEV__) { popWarningContext(); } } };
// 使用componentUpdateFn创建effect // create reactive effect for rendering const effect = (instance.effect = newReactiveEffect( componentUpdateFn, () =>queueJob(update), instance.scope// track it in component's effect scope ));
if ( __COMPAT__ && isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance) ) { instance.emit("hook:beforeDestroy"); }
// 停止副作用 // stop effects in component scope scope.stop();
// 关闭 update,卸载子组件树 // update may be null if a component is unmounted before its async // setup has resolved. if (update) { // so that scheduler will no longer invoke it update.active = false; unmount(subTree, instance, parentSuspense, doRemove); } // 调用unmounted钩子 // unmounted hook if (um) { queuePostRenderEffect(um, parentSuspense); } // 向后兼容:destroyed 钩子 if ( __COMPAT__ && isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance) ) { queuePostRenderEffect( () => instance.emit("hook:destroyed"), parentSuspense ); } // 更改状态为已卸载 queuePostRenderEffect(() => { instance.isUnmounted = true; }, parentSuspense);
// 处理<Suspense> // 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.pendingBranch && !parentSuspense.isUnmounted && instance.asyncDep && !instance.asyncResolved && instance.suspenseId === parentSuspense.pendingId ) { parentSuspense.deps--; if (parentSuspense.deps === 0) { parentSuspense.resolve(); } }
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) { devtoolsComponentRemoved(instance); } };