UNPKG

@dark-engine/core

Version:

The lightweight and powerful UI rendering engine without dependencies and written in TypeScript (Browser, Node.js, Android, iOS, Windows, Linux, macOS)

196 lines (195 loc) 6.09 kB
import { DELETE_EFFECT_TAG, UPDATE_EFFECT_TAG, SKIP_EFFECT_TAG, EFFECT_HOST_MASK, ATOM_HOST_MASK, MOVE_MASK, HOOK_DELIMETER, } from '../constants'; import { getElementKey, hasChildrenProp } from '../view'; import { Fiber } from '../fiber'; import { createIndexKey } from '../utils'; import { detectIsMemo } from '../memo'; function walk(fiber, onWalk) { let shouldDeep = true; let shouldStop = false; const skip = () => (shouldDeep = false); const stop = () => (shouldStop = true); const stack = [fiber]; while (stack.length !== 0) { const unit = stack.pop(); onWalk(unit, skip, stop); if (shouldStop) break; unit !== fiber && unit.next && stack.push(unit.next); shouldDeep && unit.child && stack.push(unit.child); shouldDeep = true; } } function collectElements(fiber, transform) { const elements = []; walk(fiber, onWalkInCollectElements(elements, transform)); return elements; } function onWalkInCollectElements(elements, transform) { return (fiber, skip) => { if (fiber.el) { !fiber.hook?.getIsPortal() && elements.push(transform(fiber)); return skip(); } }; } function getFiberWithElement(fiber) { let $fiber = fiber; while ($fiber) { if ($fiber.el) return $fiber; $fiber = $fiber.parent; } return $fiber; } function detectIsFiberAlive(fiber) { let $fiber = fiber; while ($fiber) { if ($fiber.tag === DELETE_EFFECT_TAG) return false; $fiber = $fiber.parent; } return Boolean(fiber); } function getSuspense(fiber, isPending) { let suspense = fiber; while (suspense) { if (suspense.hook?.getIsSuspense() && (isPending ? suspense.hook.getIsPending() : true)) return suspense; suspense = suspense.parent; } return null; } function resolveSuspense(fiber) { return getSuspense(fiber, true) || getSuspense(fiber, false) || null; } function resolveBoundary(fiber) { let boundary = fiber; while (boundary) { if (boundary.hook?.getIsBoundary()) return boundary; boundary = boundary.parent; } return null; } function createHookLoc(rootId, idx, hook) { const fiber = hook.owner; let $fiber = fiber; let loc = `${fiber.idx}${HOOK_DELIMETER}${idx}`; while ($fiber) { $fiber = $fiber.parent; $fiber && (loc = `${$fiber.idx}.${loc}`); } loc = `[${rootId}]${loc}`; return loc; } const createLoc = (rootId, idx, hook) => () => createHookLoc(rootId, idx, hook); function detectIsStableMemoTree(fiber, $scope) { if (!hasChildrenProp(fiber.inst)) return; const store = $scope.getReconciler().get(fiber.id); const children = fiber.inst.children; for (let i = 0; i < children.length; i++) { const inst = children[i]; const key = getElementKey(inst); if (key === null) return false; const alt = store.map[key]; if (!alt) return false; const pc = alt.inst; const nc = inst; const isStable = detectIsMemo(nc) && detectIsMemo(pc) && nc.type === pc.type && !nc.shouldUpdate(pc.props, nc.props); if (!isStable) return false; } return true; } function tryOptStaticSlot(fiber, alt, $scope) { const store = $scope.getReconciler().get(fiber.id); const inst = fiber.inst; alt.el && (fiber.el = alt.el); for (let i = 0; i < inst.children.length; i++) { buildChildNode(inst.children, fiber, store.map, i, fiber.eidx); } fiber.cc = inst.children.length; $scope.setMountDeep(false); } function tryOptMemoSlot(fiber, alt, $scope) { const store = $scope.getReconciler().get(fiber.id); const hasMove = Boolean(store.move); const hasRemove = Boolean(store.remove); const hasInsert = Boolean(store.insert); const hasReplace = Boolean(store.replace); const canOptimize = ((hasMove && !hasRemove) || (hasRemove && !hasMove)) && !hasInsert && !hasReplace; if (!canOptimize || !detectIsStableMemoTree(fiber, $scope)) return; hasMove && tryOptMov(fiber, alt, $scope); hasRemove && buildChildNodes(fiber, alt, $scope); } function tryOptMov(fiber, alt, $scope) { const store = $scope.getReconciler().get(fiber.id); buildChildNodes(fiber, alt, $scope, (fiber, key) => { if (!store.move[key]) return; fiber.alt = new Fiber().mutate(fiber); fiber.tag = UPDATE_EFFECT_TAG; fiber.mask |= MOVE_MASK; $scope.addCandidate(fiber); }); } function buildChildNodes(fiber, alt, $scope, onNode) { const store = $scope.getReconciler().get(fiber.id); const inst = fiber.inst; const children = inst.children; alt.el && (fiber.el = alt.el); for (let i = 0; i < children.length; i++) { const key = getKey(children[i], i); const $fiber = store.map[key]; buildChildNode(children, fiber, store.map, i, fiber.eidx); onNode && onNode($fiber, key); } fiber.cc = children.length; $scope.setMountDeep(false); } function buildChildNode(children, parent, altMap, idx, startEidx) { const prevIdx = idx - 1; const nextIdx = idx + 1; const key = getKey(children[idx], idx); const prevKey = getKey(children[prevIdx], prevIdx); const nextKey = getKey(children[nextIdx], nextIdx); const fiber = altMap[key]; const left = altMap[prevKey]; const right = altMap[nextKey]; const isFirst = idx === 0; const isLast = idx === children.length - 1; isFirst && (parent.child = fiber); fiber.alt = null; fiber.parent = parent; fiber.tag = SKIP_EFFECT_TAG; fiber.idx = idx; left ? (fiber.eidx = left.eidx + (left.el ? 1 : left.cec)) : (fiber.eidx = startEidx); right && (fiber.next = right); isLast && (fiber.next = null); notifyParents(fiber); } function getKey(inst, idx) { const key = getElementKey(inst); return key !== null ? key : createIndexKey(idx); } function notifyParents(fiber, alt = fiber) { fiber.increment(alt.el ? 1 : alt.cec); alt.mask & EFFECT_HOST_MASK && fiber.markHost(EFFECT_HOST_MASK); alt.mask & ATOM_HOST_MASK && fiber.markHost(ATOM_HOST_MASK); } export { walk, collectElements, getFiberWithElement, detectIsFiberAlive, resolveSuspense, resolveBoundary, createHookLoc, createLoc, tryOptStaticSlot, tryOptMemoSlot, notifyParents, }; //# sourceMappingURL=walk.js.map