UNPKG

react-fiber-keep-alive

Version:
571 lines (570 loc) 19.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.replaceFiber = exports.protectFiber = exports.appendFiberEffect = exports.traverseFiber = exports.findParentFiber = exports.findFiber = exports.getRootFiber = exports.getElementFiber = exports.HookEffectTag = exports.FiberFlag = exports.FiberMode = exports.FiberTag = exports.FiberVisit = exports.markClassComponentHasSideEffectRender = exports.markEffectHookIsOnetime = void 0; const react_1 = require("react"); const v16 = react_1.version.startsWith('16'); const v17 = react_1.version.startsWith('17'); const v18 = react_1.version.startsWith('18'); const DomPropPrefix = v16 ? '__reactInternalInstance$' : '__reactFiber$'; const FiberEffectProp = (v16 ? 'effectTag' : 'flags'); const UseSubtreeFlags = v18; const UseDeepDetach = v18; const hasOwnProperty = Object.prototype.hasOwnProperty; const isFunction = (object) => { return 'function' === typeof object; }; const EffectProp = Symbol('Effect'); const markEffectHookIsOnetime = (effect) => { return Object.defineProperty(effect, EffectProp, { configurable: true, enumerable: false, get() { return true; }, }); }; exports.markEffectHookIsOnetime = markEffectHookIsOnetime; const markClassComponentHasSideEffectRender = (Class) => { return Object.defineProperty(Class, EffectProp, { configurable: true, enumerable: false, get() { return true; }, }); }; exports.markClassComponentHasSideEffectRender = markClassComponentHasSideEffectRender; const ProtectedFiberProps = [ 'alternate', 'child', 'sibling', 'return', 'dependencies', 'pendingProps', 'memoizedProps', 'memoizedState', 'stateNode', 'updateQueue', '_debugOwner', ]; exports.FiberVisit = Object.freeze({ Child: 0b00000001, Sibling: 0b00000010, Return: 0b00000100, Effect: 0b00001000, SiblingFirst: 0b00010000, Break: 0b01000000, Continue: 0b00100000, }); exports.FiberTag = Object.freeze({ FunctionComponent: 0, ClassComponent: 1, HostRoot: 3, HostPortal: 4, HostComponent: 5, HostText: 6, ForwardRef: 11, MemoComponent: 14, SimpleMemoComponent: 15, }); exports.FiberMode = Object.freeze({ NoMode: 0, ConcurrentMode: v18 ? 1 : 4, }); exports.FiberFlag = Object.freeze({ NoFlags: 0, Placement: 2, Update: 4, Ref: v18 ? 512 : 128, Passive: v18 ? 2048 : 512, LifecycleEffectMask: v18 ? 32256 : 932, PassiveMask: v18 ? 2064 : v17 ? 520 : 0, }); exports.HookEffectTag = Object.freeze({ NoFlags: 0, HasEffect: 1, Insertion: v18 ? 2 : 0, Layout: v18 ? 4 : 2, Passive: v18 ? 8 : 4, }); const getInternalKey = (element, prefix) => { return element && Object.keys(element).find((key) => key.startsWith(prefix)); }; const getElementFiber = (element, prefix = DomPropPrefix) => { const internalKey = getInternalKey(element, prefix); return internalKey ? element[internalKey] : undefined; }; exports.getElementFiber = getElementFiber; const getRootFiber = (container) => { var _a; const fiber = v18 && container && (0, exports.getElementFiber)(container, '__reactContainer$'); const root = fiber ? fiber.stateNode : (_a = container === null || container === void 0 ? void 0 : container._reactRootContainer) === null || _a === void 0 ? void 0 : _a._internalRoot; return root === null || root === void 0 ? void 0 : root.current; }; exports.getRootFiber = getRootFiber; const getHostNode = (fiber) => { switch (fiber === null || fiber === void 0 ? void 0 : fiber.tag) { case exports.FiberTag.HostComponent: case exports.FiberTag.HostText: return fiber.stateNode; case exports.FiberTag.HostRoot: case exports.FiberTag.HostPortal: return fiber.stateNode.containerInfo; } return null; }; const isNodeFiber = (fiber) => { switch (fiber.tag) { case exports.FiberTag.HostComponent: case exports.FiberTag.HostText: return true; } return false; }; const isHostFiber = (fiber) => { switch (fiber.tag) { case exports.FiberTag.HostRoot: case exports.FiberTag.HostPortal: return true; } return false; }; const pushVisitStack = (stack, scope, fiber, flags, callback) => { if (flags === exports.FiberVisit.Return) { fiber.return && stack.push(fiber.return); return; } if (flags === exports.FiberVisit.Effect) { fiber.nextEffect && stack.push(fiber.nextEffect); return; } if (scope !== fiber && (flags & ~exports.FiberVisit.SiblingFirst) === exports.FiberVisit.Child) { flags |= exports.FiberVisit.Sibling; } const siblingFirst = flags & exports.FiberVisit.SiblingFirst; if (flags & exports.FiberVisit.Sibling) { siblingFirst || fiber.sibling && stack.push(fiber.sibling); } if (isFunction(callback)) { stack.push(callback); } if (flags & exports.FiberVisit.Child) { fiber.child && stack.push(fiber.child); } if (flags & exports.FiberVisit.Sibling) { siblingFirst && fiber.sibling && stack.push(fiber.sibling); } }; const findFiber = (fiber, predicate, flags = exports.FiberVisit.Child) => { if (!fiber) { return null; } const stack = [fiber]; while (stack.length) { const current = stack.pop(); if (!current) { continue; } const value = predicate(current); if (value === exports.FiberVisit.Break) { break; } if (value === exports.FiberVisit.Continue) { continue; } if (value) { return current; } pushVisitStack(stack, fiber, current, flags, null); } return null; }; exports.findFiber = findFiber; const findParentFiber = (fiber, Component) => (0, exports.findFiber)(fiber === null || fiber === void 0 ? void 0 : fiber.return, (node) => { return node.elementType === Component; }, exports.FiberVisit.Return); exports.findParentFiber = findParentFiber; const traverseFiber = (fiber, visit, flags = exports.FiberVisit.Child) => { if (!fiber) { return; } const stack = [fiber]; while (stack.length) { const current = stack.pop(); if (!current) { continue; } if (isFunction(current)) { current(); continue; } const value = visit(current); if (value === exports.FiberVisit.Break) { break; } if (value === exports.FiberVisit.Continue) { continue; } pushVisitStack(stack, fiber, current, flags, value); } }; exports.traverseFiber = traverseFiber; const findNextHostFiber = (scope, target) => { let current = target; while (current) { if (current === scope) { return null; } const found = (0, exports.findFiber)(current.sibling, (fiber) => { if (isHostFiber(fiber)) { return exports.FiberVisit.Continue; } return isNodeFiber(fiber); }, exports.FiberVisit.Child | exports.FiberVisit.Sibling); if (found) { return found; } current = current.return; } return null; }; const replaceFiberOnParent = (parentFiber, oldFiber, newFiber) => { let child = parentFiber.child; while (child) { if (child.sibling === oldFiber) { child.sibling = newFiber; break; } child = child.sibling; if (!child) { parentFiber.child = newFiber; } } newFiber.return = parentFiber; if (oldFiber) { newFiber.sibling = oldFiber.sibling; if (oldFiber._debugOwner) { newFiber._debugOwner = oldFiber._debugOwner; } } return parentFiber; }; const traverseEffectHooks = (fiber, visit) => { switch (fiber.tag) { case exports.FiberTag.FunctionComponent: case exports.FiberTag.ForwardRef: case exports.FiberTag.MemoComponent: case exports.FiberTag.SimpleMemoComponent: { break; } default: return false; } const updateQueue = fiber.updateQueue; const lastEffect = updateQueue === null || updateQueue === void 0 ? void 0 : updateQueue.lastEffect; let nextEffect = updateQueue === null || updateQueue === void 0 ? void 0 : updateQueue.lastEffect; while (nextEffect) { if (null != (nextEffect === null || nextEffect === void 0 ? void 0 : nextEffect.tag)) { let match = exports.HookEffectTag.NoFlags; match || (match = nextEffect.tag & exports.HookEffectTag.Insertion); match || (match = nextEffect.tag & exports.HookEffectTag.Layout); match || (match = nextEffect.tag & exports.HookEffectTag.Passive); if (match) { visit(nextEffect, match); } } nextEffect = nextEffect.next; if (nextEffect === lastEffect) { break; } } return true; }; const isNoEffectHook = (effect) => { const create = effect.create; if (!create) { return true; } if (!hasOwnProperty.call(create, EffectProp)) { return false; } return exports.HookEffectTag.NoFlags === (effect.tag & exports.HookEffectTag.HasEffect); }; const applyFiberEffect = (fiber) => { let flags = exports.FiberFlag.NoFlags; if (null != fiber.ref) { flags |= exports.FiberFlag.Ref; } const isFunctionComponent = traverseEffectHooks(fiber, (effect, type) => { if (isNoEffectHook(effect)) { return; } effect.tag |= exports.HookEffectTag.HasEffect; effect.destroy = undefined; switch (type) { case exports.HookEffectTag.Insertion: case exports.HookEffectTag.Layout: flags |= exports.FiberFlag.Update; break; case exports.HookEffectTag.Passive: flags |= exports.FiberFlag.Update; flags |= exports.FiberFlag.Passive; break; } }); if (isFunctionComponent) { return flags; } switch (fiber.tag) { case exports.FiberTag.ClassComponent: { if (!fiber.alternate) { break; } const instance = fiber.stateNode; const noUpdate = exports.FiberFlag.NoFlags === (fiber.flags & exports.FiberFlag.Update); const needRender = noUpdate && (true === instance.constructor[EffectProp]); if (needRender || isFunction(instance === null || instance === void 0 ? void 0 : instance.componentDidMount)) { flags |= exports.FiberFlag.Update; const propKey = 'componentDidUpdate'; const propVal = instance[propKey]; const ownProp = hasOwnProperty.call(instance, propKey); instance[propKey] = function simulateComponentDidMount() { var _a; if (ownProp) { instance[propKey] = propVal; } else { delete instance[propKey]; } if (needRender) { this.render(); } (_a = this.componentDidMount) === null || _a === void 0 ? void 0 : _a.call(this); }; break; } } } return flags; }; const bubbleProperties = (fiber) => { let child = fiber.child; while (child) { fiber.subtreeFlags |= child.subtreeFlags; fiber.subtreeFlags |= child.flags; child = child.sibling; } }; const appendFiberEffect = (rootFiber, effectFiber, renderFiber, finishFiber) => { if (UseSubtreeFlags) { (0, exports.traverseFiber)(renderFiber, (fiber) => { const flags = applyFiberEffect(fiber); fiber.flags |= flags; return fiber.child && (() => { bubbleProperties(fiber); }); }); effectFiber.subtreeFlags &= exports.FiberFlag.PassiveMask; } else { effectFiber.nextEffect = finishFiber; (0, exports.traverseFiber)(renderFiber, (fiber) => { if (fiber === finishFiber) { return exports.FiberVisit.Continue; } const flags = applyFiberEffect(fiber); rootFiber[FiberEffectProp] |= flags; fiber[FiberEffectProp] |= flags; fiber.nextEffect = null; if (exports.FiberFlag.NoFlags !== (fiber[FiberEffectProp] & exports.FiberFlag.LifecycleEffectMask)) { const nextEffect = effectFiber.nextEffect; effectFiber.nextEffect = fiber; fiber.nextEffect = nextEffect; } return null; }, exports.FiberVisit.Child | exports.FiberVisit.SiblingFirst); } }; exports.appendFiberEffect = appendFiberEffect; const defineFiberProp = (fiber, prop, value) => { Object.defineProperty(fiber, prop, { configurable: true, enumerable: true, writable: true, value, }); }; const protectFiberProp = (fiber, prop, restore) => { if (null == fiber) { return; } const descriptor = Object.getOwnPropertyDescriptor(fiber, prop); if (!descriptor || descriptor.get) { return; } const backup = fiber[prop]; let value = backup; if (prop === 'stateNode' && isNodeFiber(fiber)) { value = value && document.createElement('dummy'); } Object.defineProperty(fiber, prop, { configurable: true, enumerable: true, get() { return restore.current ? backup : value; }, set(update) { value = update; }, }); }; const restoreFiberProp = (fiber, prop) => { if (hasOwnProperty.call(fiber, prop)) { defineFiberProp(fiber, prop, fiber[prop]); } }; const protectFiberProps = (fiber, current, restore) => { const alternate = fiber.alternate; if (alternate && !current) { protectFiberProps(alternate, fiber, restore); } for (const prop of ProtectedFiberProps) { protectFiberProp(fiber, prop, restore); } }; const restoreFiberProps = (fiber, current) => { restoreFiberProp(fiber, 'alternate'); const alternate = fiber.alternate; if (alternate && !current) { restoreFiberProps(alternate, fiber); } for (const prop of ProtectedFiberProps) { if (prop !== 'alternate') { restoreFiberProp(fiber, prop); } } }; const protectFiber = (fiber) => { const restore = { current: true }; const stack = [true]; (0, exports.traverseFiber)(fiber, (node) => { const needDetach = isNodeFiber(node) && true === stack[0]; if (needDetach) { const hostNode = getHostNode(node); const parentNode = hostNode === null || hostNode === void 0 ? void 0 : hostNode.parentNode; protectFiberProp(node, 'stateNode', restore); if (hostNode && parentNode) { const dummy = document.createElement('dummy'); parentNode.replaceChild(dummy, hostNode); node.stateNode = dummy; } } if (UseDeepDetach || node === fiber) { protectFiberProps(node, null, restore); } if (node.tag === exports.FiberTag.HostPortal) { stack.unshift(true); return () => stack.shift(); } if (needDetach) { stack.unshift(false); return () => stack.shift(); } return null; }); restore.current = false; return restore; }; exports.protectFiber = protectFiber; const replaceFiber = (oldFiber, newFiber, restore) => { const parentFiber = oldFiber.return; if (!parentFiber) { return false; } const containerFiber = (0, exports.findFiber)(parentFiber, (fiber) => { return isNodeFiber(fiber) || isHostFiber(fiber); }, exports.FiberVisit.Return); const containerNode = getHostNode(containerFiber); if (!containerFiber || !containerNode) { return false; } const stack = [containerFiber]; (0, exports.traverseFiber)(oldFiber, (fiber) => { const hostFiber = stack[0]; const needDetach = isNodeFiber(fiber) && null != hostFiber; if (needDetach) { const parentNode = getHostNode(hostFiber); const childNode = getHostNode(fiber); if (parentNode && childNode) { parentNode.removeChild(childNode); } } if (fiber.tag === exports.FiberTag.HostPortal) { stack.unshift(fiber); return () => stack.shift(); } if (needDetach) { stack.unshift(null); return () => stack.shift(); } return null; }); const nextHostFiber = findNextHostFiber(containerFiber, oldFiber); const nextHostNode = getHostNode(nextHostFiber); restore.current = true; (0, exports.traverseFiber)(newFiber, (fiber) => { const hostFiber = stack[0]; const needAttach = isNodeFiber(fiber) && null != hostFiber; if (needAttach) { restoreFiberProp(fiber, 'stateNode'); const parentNode = getHostNode(hostFiber); const childNode = getHostNode(fiber); if (parentNode && childNode) { if (!nextHostNode || hostFiber.tag === exports.FiberTag.HostPortal) { parentNode.appendChild(childNode); } else { parentNode.insertBefore(childNode, nextHostNode); } } } if (UseDeepDetach || fiber === newFiber) { restoreFiberProps(fiber, null); } traverseEffectHooks(fiber, (effect) => { effect.tag &= ~exports.HookEffectTag.HasEffect; effect.destroy = undefined; }); if (fiber.tag === exports.FiberTag.HostPortal) { stack.unshift(fiber); return () => stack.shift(); } if (needAttach) { stack.unshift(null); return () => stack.shift(); } return null; }); restore.current = false; replaceFiberOnParent(parentFiber, oldFiber, newFiber); if (!newFiber.alternate || !parentFiber.alternate) { if (newFiber.alternate) { newFiber.alternate.return = null; newFiber.alternate.sibling = null; } } else { replaceFiberOnParent(parentFiber.alternate, oldFiber.alternate, newFiber.alternate); } return true; }; exports.replaceFiber = replaceFiber;