295 lines
8.5 KiB
JavaScript
295 lines
8.5 KiB
JavaScript
/**
|
|
* 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
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
import type {Fiber} from 'react-reconciler/src/ReactFiber';
|
|
|
|
var invariant = require('fbjs/lib/invariant');
|
|
|
|
var ReactInstanceMap = require('./ReactInstanceMap');
|
|
var {ReactCurrentOwner} = require('./ReactGlobalSharedState');
|
|
var getComponentName = require('./getComponentName');
|
|
var {
|
|
ClassComponent,
|
|
HostComponent,
|
|
HostRoot,
|
|
HostPortal,
|
|
HostText,
|
|
} = require('./ReactTypeOfWork');
|
|
var {NoEffect, Placement} = require('./ReactTypeOfSideEffect');
|
|
|
|
if (__DEV__) {
|
|
var warning = require('fbjs/lib/warning');
|
|
}
|
|
|
|
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;
|
|
}
|
|
exports.isFiberMounted = function(fiber: Fiber): boolean {
|
|
return isFiberMountedImpl(fiber) === MOUNTED;
|
|
};
|
|
|
|
exports.isMounted = function(component: React$Component<any, any>): 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.',
|
|
);
|
|
}
|
|
|
|
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;
|
|
}
|
|
exports.findCurrentFiberUsingSlowPath = findCurrentFiberUsingSlowPath;
|
|
|
|
exports.findCurrentHostFiber = function(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;
|
|
};
|
|
|
|
exports.findCurrentHostFiberWithNoPortals = function(
|
|
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;
|
|
};
|