/** * Copyright (c) 2013-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @flow */ import type {Fiber} from 'react-reconciler/src/ReactFiber'; import invariant from 'fbjs/lib/invariant'; import warning from 'fbjs/lib/warning'; import * as ReactInstanceMap from './ReactInstanceMap'; import {ReactCurrentOwner} from './ReactGlobalSharedState'; import getComponentName from './getComponentName'; import { ClassComponent, HostComponent, HostRoot, HostPortal, HostText, } from './ReactTypeOfWork'; import {NoEffect, Placement} from './ReactTypeOfSideEffect'; var MOUNTING = 1; var MOUNTED = 2; var UNMOUNTED = 3; function isFiberMountedImpl(fiber: Fiber): number { let node = fiber; if (!fiber.alternate) { // If there is no alternate, this might be a new tree that isn't inserted // yet. If it is, then it will have a pending insertion effect on it. if ((node.effectTag & Placement) !== NoEffect) { return MOUNTING; } while (node.return) { node = node.return; if ((node.effectTag & Placement) !== NoEffect) { return MOUNTING; } } } else { while (node.return) { node = node.return; } } if (node.tag === HostRoot) { // TODO: Check if this was a nested HostRoot when used with // renderContainerIntoSubtree. return MOUNTED; } // If we didn't hit the root, that means that we're in an disconnected tree // that has been unmounted. return UNMOUNTED; } export function isFiberMounted(fiber: Fiber): boolean { return isFiberMountedImpl(fiber) === MOUNTED; } export function isMounted(component: React$Component): boolean { if (__DEV__) { const owner = (ReactCurrentOwner.current: any); if (owner !== null && owner.tag === ClassComponent) { const ownerFiber: Fiber = owner; const instance = ownerFiber.stateNode; warning( instance._warnedAboutRefsInRender, '%s is accessing isMounted inside its render() function. ' + 'render() should be a pure function of props and state. It should ' + 'never access something that requires stale data from the previous ' + 'render, such as refs. Move this logic to componentDidMount and ' + 'componentDidUpdate instead.', getComponentName(ownerFiber) || 'A component', ); instance._warnedAboutRefsInRender = true; } } var fiber: ?Fiber = ReactInstanceMap.get(component); if (!fiber) { return false; } return isFiberMountedImpl(fiber) === MOUNTED; } function assertIsMounted(fiber) { invariant( isFiberMountedImpl(fiber) === MOUNTED, 'Unable to find node on an unmounted component.', ); } export function findCurrentFiberUsingSlowPath(fiber: Fiber): Fiber | null { let alternate = fiber.alternate; if (!alternate) { // If there is no alternate, then we only need to check if it is mounted. const state = isFiberMountedImpl(fiber); invariant( state !== UNMOUNTED, 'Unable to find node on an unmounted component.', ); if (state === MOUNTING) { return null; } return fiber; } // If we have two possible branches, we'll walk backwards up to the root // to see what path the root points to. On the way we may hit one of the // special cases and we'll deal with them. let a = fiber; let b = alternate; while (true) { let parentA = a.return; let parentB = parentA ? parentA.alternate : null; if (!parentA || !parentB) { // We're at the root. break; } // If both copies of the parent fiber point to the same child, we can // assume that the child is current. This happens when we bailout on low // priority: the bailed out fiber's child reuses the current child. if (parentA.child === parentB.child) { let child = parentA.child; while (child) { if (child === a) { // We've determined that A is the current branch. assertIsMounted(parentA); return fiber; } if (child === b) { // We've determined that B is the current branch. assertIsMounted(parentA); return alternate; } child = child.sibling; } // We should never have an alternate for any mounting node. So the only // way this could possibly happen is if this was unmounted, if at all. invariant(false, 'Unable to find node on an unmounted component.'); } if (a.return !== b.return) { // The return pointer of A and the return pointer of B point to different // fibers. We assume that return pointers never criss-cross, so A must // belong to the child set of A.return, and B must belong to the child // set of B.return. a = parentA; b = parentB; } else { // The return pointers point to the same fiber. We'll have to use the // default, slow path: scan the child sets of each parent alternate to see // which child belongs to which set. // // Search parent A's child set let didFindChild = false; let child = parentA.child; while (child) { if (child === a) { didFindChild = true; a = parentA; b = parentB; break; } if (child === b) { didFindChild = true; b = parentA; a = parentB; break; } child = child.sibling; } if (!didFindChild) { // Search parent B's child set child = parentB.child; while (child) { if (child === a) { didFindChild = true; a = parentB; b = parentA; break; } if (child === b) { didFindChild = true; b = parentB; a = parentA; break; } child = child.sibling; } invariant( didFindChild, 'Child was not found in either parent set. This indicates a bug ' + 'in React related to the return pointer. Please file an issue.', ); } } invariant( a.alternate === b, "Return fibers should always be each others' alternates. " + 'This error is likely caused by a bug in React. Please file an issue.', ); } // If the root is not a host container, we're in a disconnected tree. I.e. // unmounted. invariant( a.tag === HostRoot, 'Unable to find node on an unmounted component.', ); if (a.stateNode.current === a) { // We've determined that A is the current branch. return fiber; } // Otherwise B has to be current branch. return alternate; } export function findCurrentHostFiber(parent: Fiber): Fiber | null { const currentParent = findCurrentFiberUsingSlowPath(parent); if (!currentParent) { return null; } // Next we'll drill down this component to find the first HostComponent/Text. let node: Fiber = currentParent; while (true) { if (node.tag === HostComponent || node.tag === HostText) { return node; } else if (node.child) { node.child.return = node; node = node.child; continue; } if (node === currentParent) { return null; } while (!node.sibling) { if (!node.return || node.return === currentParent) { return null; } node = node.return; } node.sibling.return = node.return; node = node.sibling; } // Flow needs the return null here, but ESLint complains about it. // eslint-disable-next-line no-unreachable return null; } export function findCurrentHostFiberWithNoPortals(parent: Fiber): Fiber | null { const currentParent = findCurrentFiberUsingSlowPath(parent); if (!currentParent) { return null; } // Next we'll drill down this component to find the first HostComponent/Text. let node: Fiber = currentParent; while (true) { if (node.tag === HostComponent || node.tag === HostText) { return node; } else if (node.child && node.tag !== HostPortal) { node.child.return = node; node = node.child; continue; } if (node === currentParent) { return null; } while (!node.sibling) { if (!node.return || node.return === currentParent) { return null; } node = node.return; } node.sibling.return = node.return; node = node.sibling; } // Flow needs the return null here, but ESLint complains about it. // eslint-disable-next-line no-unreachable return null; }