mirror of
https://github.com/zebrajr/react.git
synced 2026-01-15 12:15:22 +00:00
- method unbinding is no longer supported in Flow for soundness, this added a bunch of suppressions
- Flow now prevents objects to be supertypes of interfaces/classes
ghstack-source-id: d7749cbad8
Pull Request resolved: https://github.com/facebook/react/pull/25412
269 lines
9.6 KiB
JavaScript
269 lines
9.6 KiB
JavaScript
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* 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/ReactInternalTypes';
|
|
import type {ReactScopeInstance} from 'shared/ReactTypes';
|
|
import type {
|
|
ReactDOMEventHandle,
|
|
ReactDOMEventHandleListener,
|
|
} from '../shared/ReactDOMTypes';
|
|
import type {
|
|
Container,
|
|
TextInstance,
|
|
Instance,
|
|
SuspenseInstance,
|
|
Props,
|
|
} from './ReactDOMHostConfig';
|
|
|
|
import {
|
|
HostComponent,
|
|
HostResource,
|
|
HostText,
|
|
HostRoot,
|
|
SuspenseComponent,
|
|
} from 'react-reconciler/src/ReactWorkTags';
|
|
|
|
import {getParentSuspenseInstance} from './ReactDOMHostConfig';
|
|
|
|
import {enableScopeAPI, enableFloat} from 'shared/ReactFeatureFlags';
|
|
|
|
const randomKey = Math.random()
|
|
.toString(36)
|
|
.slice(2);
|
|
const internalInstanceKey = '__reactFiber$' + randomKey;
|
|
const internalPropsKey = '__reactProps$' + randomKey;
|
|
const internalContainerInstanceKey = '__reactContainer$' + randomKey;
|
|
const internalEventHandlersKey = '__reactEvents$' + randomKey;
|
|
const internalEventHandlerListenersKey = '__reactListeners$' + randomKey;
|
|
const internalEventHandlesSetKey = '__reactHandles$' + randomKey;
|
|
|
|
export function detachDeletedInstance(node: Instance): void {
|
|
// TODO: This function is only called on host components. I don't think all of
|
|
// these fields are relevant.
|
|
delete (node: any)[internalInstanceKey];
|
|
delete (node: any)[internalPropsKey];
|
|
delete (node: any)[internalEventHandlersKey];
|
|
delete (node: any)[internalEventHandlerListenersKey];
|
|
delete (node: any)[internalEventHandlesSetKey];
|
|
}
|
|
|
|
export function precacheFiberNode(
|
|
hostInst: Fiber,
|
|
node: Instance | TextInstance | SuspenseInstance | ReactScopeInstance,
|
|
): void {
|
|
(node: any)[internalInstanceKey] = hostInst;
|
|
}
|
|
|
|
export function markContainerAsRoot(hostRoot: Fiber, node: Container): void {
|
|
// $FlowFixMe[prop-missing]
|
|
node[internalContainerInstanceKey] = hostRoot;
|
|
}
|
|
|
|
export function unmarkContainerAsRoot(node: Container): void {
|
|
// $FlowFixMe[prop-missing]
|
|
node[internalContainerInstanceKey] = null;
|
|
}
|
|
|
|
export function isContainerMarkedAsRoot(node: Container): boolean {
|
|
// $FlowFixMe[prop-missing]
|
|
return !!node[internalContainerInstanceKey];
|
|
}
|
|
|
|
// Given a DOM node, return the closest HostComponent or HostText fiber ancestor.
|
|
// If the target node is part of a hydrated or not yet rendered subtree, then
|
|
// this may also return a SuspenseComponent or HostRoot to indicate that.
|
|
// Conceptually the HostRoot fiber is a child of the Container node. So if you
|
|
// pass the Container node as the targetNode, you will not actually get the
|
|
// HostRoot back. To get to the HostRoot, you need to pass a child of it.
|
|
// The same thing applies to Suspense boundaries.
|
|
export function getClosestInstanceFromNode(targetNode: Node): null | Fiber {
|
|
let targetInst = (targetNode: any)[internalInstanceKey];
|
|
if (targetInst) {
|
|
// Don't return HostRoot or SuspenseComponent here.
|
|
return targetInst;
|
|
}
|
|
// If the direct event target isn't a React owned DOM node, we need to look
|
|
// to see if one of its parents is a React owned DOM node.
|
|
let parentNode = targetNode.parentNode;
|
|
while (parentNode) {
|
|
// We'll check if this is a container root that could include
|
|
// React nodes in the future. We need to check this first because
|
|
// if we're a child of a dehydrated container, we need to first
|
|
// find that inner container before moving on to finding the parent
|
|
// instance. Note that we don't check this field on the targetNode
|
|
// itself because the fibers are conceptually between the container
|
|
// node and the first child. It isn't surrounding the container node.
|
|
// If it's not a container, we check if it's an instance.
|
|
targetInst =
|
|
(parentNode: any)[internalContainerInstanceKey] ||
|
|
(parentNode: any)[internalInstanceKey];
|
|
if (targetInst) {
|
|
// Since this wasn't the direct target of the event, we might have
|
|
// stepped past dehydrated DOM nodes to get here. However they could
|
|
// also have been non-React nodes. We need to answer which one.
|
|
|
|
// If we the instance doesn't have any children, then there can't be
|
|
// a nested suspense boundary within it. So we can use this as a fast
|
|
// bailout. Most of the time, when people add non-React children to
|
|
// the tree, it is using a ref to a child-less DOM node.
|
|
// Normally we'd only need to check one of the fibers because if it
|
|
// has ever gone from having children to deleting them or vice versa
|
|
// it would have deleted the dehydrated boundary nested inside already.
|
|
// However, since the HostRoot starts out with an alternate it might
|
|
// have one on the alternate so we need to check in case this was a
|
|
// root.
|
|
const alternate = targetInst.alternate;
|
|
if (
|
|
targetInst.child !== null ||
|
|
(alternate !== null && alternate.child !== null)
|
|
) {
|
|
// Next we need to figure out if the node that skipped past is
|
|
// nested within a dehydrated boundary and if so, which one.
|
|
let suspenseInstance = getParentSuspenseInstance(targetNode);
|
|
while (suspenseInstance !== null) {
|
|
// We found a suspense instance. That means that we haven't
|
|
// hydrated it yet. Even though we leave the comments in the
|
|
// DOM after hydrating, and there are boundaries in the DOM
|
|
// that could already be hydrated, we wouldn't have found them
|
|
// through this pass since if the target is hydrated it would
|
|
// have had an internalInstanceKey on it.
|
|
// Let's get the fiber associated with the SuspenseComponent
|
|
// as the deepest instance.
|
|
// $FlowFixMe[prop-missing]
|
|
const targetSuspenseInst = suspenseInstance[internalInstanceKey];
|
|
if (targetSuspenseInst) {
|
|
return targetSuspenseInst;
|
|
}
|
|
// If we don't find a Fiber on the comment, it might be because
|
|
// we haven't gotten to hydrate it yet. There might still be a
|
|
// parent boundary that hasn't above this one so we need to find
|
|
// the outer most that is known.
|
|
suspenseInstance = getParentSuspenseInstance(suspenseInstance);
|
|
// If we don't find one, then that should mean that the parent
|
|
// host component also hasn't hydrated yet. We can return it
|
|
// below since it will bail out on the isMounted check later.
|
|
}
|
|
}
|
|
return targetInst;
|
|
}
|
|
targetNode = parentNode;
|
|
parentNode = targetNode.parentNode;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Given a DOM node, return the ReactDOMComponent or ReactDOMTextComponent
|
|
* instance, or null if the node was not rendered by this React.
|
|
*/
|
|
export function getInstanceFromNode(node: Node): Fiber | null {
|
|
const inst =
|
|
(node: any)[internalInstanceKey] ||
|
|
(node: any)[internalContainerInstanceKey];
|
|
if (inst) {
|
|
if (
|
|
inst.tag === HostComponent ||
|
|
inst.tag === HostText ||
|
|
inst.tag === SuspenseComponent ||
|
|
inst.tag === HostRoot ||
|
|
(enableFloat ? inst.tag === HostResource : false)
|
|
) {
|
|
return inst;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Given a ReactDOMComponent or ReactDOMTextComponent, return the corresponding
|
|
* DOM node.
|
|
*/
|
|
export function getNodeFromInstance(inst: Fiber): Instance | TextInstance {
|
|
if (
|
|
inst.tag === HostComponent ||
|
|
inst.tag === HostText ||
|
|
(enableFloat ? inst.tag === HostResource : false)
|
|
) {
|
|
// In Fiber this, is just the state node right now. We assume it will be
|
|
// a host component or host text.
|
|
return inst.stateNode;
|
|
}
|
|
|
|
// Without this first invariant, passing a non-DOM-component triggers the next
|
|
// invariant for a missing parent, which is super confusing.
|
|
throw new Error('getNodeFromInstance: Invalid argument.');
|
|
}
|
|
|
|
export function getFiberCurrentPropsFromNode(
|
|
node: Instance | TextInstance | SuspenseInstance,
|
|
): Props {
|
|
return (node: any)[internalPropsKey] || null;
|
|
}
|
|
|
|
export function updateFiberProps(
|
|
node: Instance | TextInstance | SuspenseInstance,
|
|
props: Props,
|
|
): void {
|
|
(node: any)[internalPropsKey] = props;
|
|
}
|
|
|
|
export function getEventListenerSet(node: EventTarget): Set<string> {
|
|
let elementListenerSet = (node: any)[internalEventHandlersKey];
|
|
if (elementListenerSet === undefined) {
|
|
elementListenerSet = (node: any)[internalEventHandlersKey] = new Set();
|
|
}
|
|
return elementListenerSet;
|
|
}
|
|
|
|
export function getFiberFromScopeInstance(
|
|
scope: ReactScopeInstance,
|
|
): null | Fiber {
|
|
if (enableScopeAPI) {
|
|
return (scope: any)[internalInstanceKey] || null;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export function setEventHandlerListeners(
|
|
scope: EventTarget | ReactScopeInstance,
|
|
listeners: Set<ReactDOMEventHandleListener>,
|
|
): void {
|
|
(scope: any)[internalEventHandlerListenersKey] = listeners;
|
|
}
|
|
|
|
export function getEventHandlerListeners(
|
|
scope: EventTarget | ReactScopeInstance,
|
|
): null | Set<ReactDOMEventHandleListener> {
|
|
return (scope: any)[internalEventHandlerListenersKey] || null;
|
|
}
|
|
|
|
export function addEventHandleToTarget(
|
|
target: EventTarget | ReactScopeInstance,
|
|
eventHandle: ReactDOMEventHandle,
|
|
): void {
|
|
let eventHandles = (target: any)[internalEventHandlesSetKey];
|
|
if (eventHandles === undefined) {
|
|
eventHandles = (target: any)[internalEventHandlesSetKey] = new Set();
|
|
}
|
|
eventHandles.add(eventHandle);
|
|
}
|
|
|
|
export function doesTargetHaveEventHandle(
|
|
target: EventTarget | ReactScopeInstance,
|
|
eventHandle: ReactDOMEventHandle,
|
|
): boolean {
|
|
const eventHandles = (target: any)[internalEventHandlesSetKey];
|
|
if (eventHandles === undefined) {
|
|
return false;
|
|
}
|
|
return eventHandles.has(eventHandle);
|
|
}
|