mirror of
https://github.com/zebrajr/react.git
synced 2025-12-07 12:20:38 +01:00
Refactor Enter/Leave listener accumulation (#18405)
* Refactor Enter/Leave listener accumulation
This commit is contained in:
parent
c80cd8ee27
commit
663c13d71d
|
|
@ -1,76 +0,0 @@
|
|||
/**
|
||||
* 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/ReactFiber';
|
||||
import type {ReactSyntheticEvent} from 'legacy-events/ReactSyntheticEventType';
|
||||
|
||||
import getListener from 'legacy-events/getListener';
|
||||
|
||||
import {traverseEnterLeave} from 'react-reconciler/src/ReactTreeTraversal';
|
||||
import accumulateInto from './accumulateInto';
|
||||
import forEachAccumulated from './forEachAccumulated';
|
||||
|
||||
/**
|
||||
* A small set of propagation patterns, each of which will accept a small amount
|
||||
* of information, and generate a set of "dispatch ready event objects" - which
|
||||
* are sets of events that have already been annotated with a set of dispatched
|
||||
* listener functions/ids. The API is designed this way to discourage these
|
||||
* propagation strategies from actually executing the dispatches, since we
|
||||
* always want to collect the entire set of dispatches before executing even a
|
||||
* single one.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Accumulates without regard to direction, does not look for phased
|
||||
* registration names. Same as `accumulateDirectDispatchesSingle` but without
|
||||
* requiring that the `dispatchMarker` be the same as the dispatched ID.
|
||||
*/
|
||||
function accumulateDispatches(
|
||||
inst: Fiber,
|
||||
ignoredDirection: ?boolean,
|
||||
event: ReactSyntheticEvent,
|
||||
): void {
|
||||
if (inst && event && event.dispatchConfig.registrationName) {
|
||||
const registrationName = event.dispatchConfig.registrationName;
|
||||
const listener = getListener(inst, registrationName);
|
||||
if (listener) {
|
||||
event._dispatchListeners = accumulateInto(
|
||||
event._dispatchListeners,
|
||||
listener,
|
||||
);
|
||||
event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accumulates dispatches on an `SyntheticEvent`, but only for the
|
||||
* `dispatchMarker`.
|
||||
* @param {SyntheticEvent} event
|
||||
*/
|
||||
function accumulateDirectDispatchesSingle(event: ReactSyntheticEvent) {
|
||||
if (event && event.dispatchConfig.registrationName) {
|
||||
accumulateDispatches(event._targetInst, null, event);
|
||||
}
|
||||
}
|
||||
|
||||
export function accumulateEnterLeaveDispatches(
|
||||
leave: ReactSyntheticEvent,
|
||||
enter: ReactSyntheticEvent,
|
||||
from: Fiber,
|
||||
to: Fiber,
|
||||
) {
|
||||
traverseEnterLeave(from, to, accumulateDispatches, leave, enter);
|
||||
}
|
||||
|
||||
export function accumulateDirectDispatches(
|
||||
events: ?(Array<ReactSyntheticEvent> | ReactSyntheticEvent),
|
||||
) {
|
||||
forEachAccumulated(events, accumulateDirectDispatchesSingle);
|
||||
}
|
||||
|
|
@ -27,6 +27,7 @@ export type CustomDispatchConfig = {|
|
|||
bubbled: null,
|
||||
captured: null,
|
||||
|},
|
||||
registrationName?: string,
|
||||
customEvent: true,
|
||||
|};
|
||||
|
||||
|
|
|
|||
|
|
@ -5,20 +5,12 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {
|
||||
getLowestCommonAncestor,
|
||||
isAncestor,
|
||||
getParentInstance,
|
||||
traverseTwoPhase,
|
||||
} from 'react-reconciler/src/ReactTreeTraversal';
|
||||
|
||||
import {
|
||||
executeDirectDispatch,
|
||||
hasDispatches,
|
||||
executeDispatchesInOrderStopAtTrue,
|
||||
getInstanceFromNode,
|
||||
} from './EventPluginUtils';
|
||||
import {accumulateDirectDispatches} from './EventPropagators';
|
||||
import ResponderSyntheticEvent from './ResponderSyntheticEvent';
|
||||
import ResponderTouchHistoryStore from './ResponderTouchHistoryStore';
|
||||
import accumulate from './accumulate';
|
||||
|
|
@ -36,6 +28,7 @@ import {
|
|||
import getListener from './getListener';
|
||||
import accumulateInto from './accumulateInto';
|
||||
import forEachAccumulated from './forEachAccumulated';
|
||||
import {HostComponent} from 'react-reconciler/src/ReactWorkTags';
|
||||
|
||||
/**
|
||||
* Instance of element that should respond to touch/move types of interactions,
|
||||
|
|
@ -158,6 +151,91 @@ const eventTypes = {
|
|||
// Start of inline: the below functions were inlined from
|
||||
// EventPropagator.js, as they deviated from ReactDOM's newer
|
||||
// implementations.
|
||||
|
||||
function getParent(inst) {
|
||||
do {
|
||||
inst = inst.return;
|
||||
// TODO: If this is a HostRoot we might want to bail out.
|
||||
// That is depending on if we want nested subtrees (layers) to bubble
|
||||
// events to their parent. We could also go through parentNode on the
|
||||
// host node but that wouldn't work for React Native and doesn't let us
|
||||
// do the portal feature.
|
||||
} while (inst && inst.tag !== HostComponent);
|
||||
if (inst) {
|
||||
return inst;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the lowest common ancestor of A and B, or null if they are in
|
||||
* different trees.
|
||||
*/
|
||||
export function getLowestCommonAncestor(instA, instB) {
|
||||
let depthA = 0;
|
||||
for (let tempA = instA; tempA; tempA = getParent(tempA)) {
|
||||
depthA++;
|
||||
}
|
||||
let depthB = 0;
|
||||
for (let tempB = instB; tempB; tempB = getParent(tempB)) {
|
||||
depthB++;
|
||||
}
|
||||
|
||||
// If A is deeper, crawl up.
|
||||
while (depthA - depthB > 0) {
|
||||
instA = getParent(instA);
|
||||
depthA--;
|
||||
}
|
||||
|
||||
// If B is deeper, crawl up.
|
||||
while (depthB - depthA > 0) {
|
||||
instB = getParent(instB);
|
||||
depthB--;
|
||||
}
|
||||
|
||||
// Walk in lockstep until we find a match.
|
||||
let depth = depthA;
|
||||
while (depth--) {
|
||||
if (instA === instB || instA === instB.alternate) {
|
||||
return instA;
|
||||
}
|
||||
instA = getParent(instA);
|
||||
instB = getParent(instB);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if A is an ancestor of B.
|
||||
*/
|
||||
export function isAncestor(instA, instB) {
|
||||
while (instB) {
|
||||
if (instA === instB || instA === instB.alternate) {
|
||||
return true;
|
||||
}
|
||||
instB = getParent(instB);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates the traversal of a two-phase, capture/bubble event dispatch.
|
||||
*/
|
||||
export function traverseTwoPhase(inst, fn, arg) {
|
||||
const path = [];
|
||||
while (inst) {
|
||||
path.push(inst);
|
||||
inst = getParent(inst);
|
||||
}
|
||||
let i;
|
||||
for (i = path.length; i-- > 0; ) {
|
||||
fn(path[i], 'captured', arg);
|
||||
}
|
||||
for (i = 0; i < path.length; i++) {
|
||||
fn(path[i], 'bubbled', arg);
|
||||
}
|
||||
}
|
||||
|
||||
function listenerAtPhase(inst, event, propagationPhase: PropagationPhases) {
|
||||
const registrationName =
|
||||
event.dispatchConfig.phasedRegistrationNames[propagationPhase];
|
||||
|
|
@ -180,10 +258,48 @@ function accumulateDirectionalDispatches(inst, phase, event) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accumulates without regard to direction, does not look for phased
|
||||
* registration names. Same as `accumulateDirectDispatchesSingle` but without
|
||||
* requiring that the `dispatchMarker` be the same as the dispatched ID.
|
||||
*/
|
||||
function accumulateDispatches(
|
||||
inst: Object,
|
||||
ignoredDirection: ?boolean,
|
||||
event: Object,
|
||||
): void {
|
||||
if (inst && event && event.dispatchConfig.registrationName) {
|
||||
const registrationName = event.dispatchConfig.registrationName;
|
||||
const listener = getListener(inst, registrationName);
|
||||
if (listener) {
|
||||
event._dispatchListeners = accumulateInto(
|
||||
event._dispatchListeners,
|
||||
listener,
|
||||
);
|
||||
event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accumulates dispatches on an `SyntheticEvent`, but only for the
|
||||
* `dispatchMarker`.
|
||||
* @param {SyntheticEvent} event
|
||||
*/
|
||||
function accumulateDirectDispatchesSingle(event: Object) {
|
||||
if (event && event.dispatchConfig.registrationName) {
|
||||
accumulateDispatches(event._targetInst, null, event);
|
||||
}
|
||||
}
|
||||
|
||||
function accumulateDirectDispatches(events: ?(Array<Object> | Object)) {
|
||||
forEachAccumulated(events, accumulateDirectDispatchesSingle);
|
||||
}
|
||||
|
||||
function accumulateTwoPhaseDispatchesSingleSkipTarget(event) {
|
||||
if (event && event.dispatchConfig.phasedRegistrationNames) {
|
||||
const targetInst = event._targetInst;
|
||||
const parentInst = targetInst ? getParentInstance(targetInst) : null;
|
||||
const parentInst = targetInst ? getParent(targetInst) : null;
|
||||
traverseTwoPhase(parentInst, accumulateDirectionalDispatches, event);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1378,7 +1378,8 @@ describe('ResponderEventPlugin', () => {
|
|||
// ResponderEventPlugin uses `getLowestCommonAncestor`
|
||||
const React = require('react');
|
||||
const ReactTestUtils = require('react-dom/test-utils');
|
||||
const ReactTreeTraversal = require('react-reconciler/src/ReactTreeTraversal');
|
||||
const getLowestCommonAncestor = require('legacy-events/ResponderEventPlugin')
|
||||
.getLowestCommonAncestor;
|
||||
const ReactDOMComponentTree = require('../../react-dom/src/client/ReactDOMComponentTree');
|
||||
|
||||
class ChildComponent extends React.Component {
|
||||
|
|
@ -1451,7 +1452,7 @@ describe('ResponderEventPlugin', () => {
|
|||
let i;
|
||||
for (i = 0; i < ancestors.length; i++) {
|
||||
const plan = ancestors[i];
|
||||
const firstCommon = ReactTreeTraversal.getLowestCommonAncestor(
|
||||
const firstCommon = getLowestCommonAncestor(
|
||||
ReactDOMComponentTree.getInstanceFromNode(plan.one),
|
||||
ReactDOMComponentTree.getInstanceFromNode(plan.two),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {accumulateEnterLeaveDispatches} from 'legacy-events/EventPropagators';
|
||||
|
||||
import {
|
||||
TOP_MOUSE_OUT,
|
||||
TOP_MOUSE_OVER,
|
||||
|
|
@ -23,6 +21,7 @@ import {
|
|||
import {HostComponent, HostText} from 'react-reconciler/src/ReactWorkTags';
|
||||
import {getNearestMountedFiber} from 'react-reconciler/src/ReactFiberTreeReflection';
|
||||
import {enableModernEventSystem} from 'shared/ReactFeatureFlags';
|
||||
import accumulateEnterLeaveListeners from './accumulateEnterLeaveListeners';
|
||||
|
||||
const eventTypes = {
|
||||
mouseEnter: {
|
||||
|
|
@ -172,7 +171,7 @@ const EnterLeaveEventPlugin = {
|
|||
enter.target = toNode;
|
||||
enter.relatedTarget = fromNode;
|
||||
|
||||
accumulateEnterLeaveDispatches(leave, enter, from, to);
|
||||
accumulateEnterLeaveListeners(leave, enter, from, to);
|
||||
|
||||
if (!enableModernEventSystem) {
|
||||
// If we are not processing the first ancestor, then we
|
||||
|
|
|
|||
139
packages/react-dom/src/events/accumulateEnterLeaveListeners.js
vendored
Normal file
139
packages/react-dom/src/events/accumulateEnterLeaveListeners.js
vendored
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
/**
|
||||
* 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/ReactFiber';
|
||||
import type {ReactSyntheticEvent} from 'legacy-events/ReactSyntheticEventType';
|
||||
|
||||
import getListener from 'legacy-events/getListener';
|
||||
import {HostComponent} from 'react-reconciler/src/ReactWorkTags';
|
||||
|
||||
function getParent(inst: Fiber | null): Fiber | null {
|
||||
if (inst === null) {
|
||||
return null;
|
||||
}
|
||||
do {
|
||||
inst = inst.return;
|
||||
// TODO: If this is a HostRoot we might want to bail out.
|
||||
// That is depending on if we want nested subtrees (layers) to bubble
|
||||
// events to their parent. We could also go through parentNode on the
|
||||
// host node but that wouldn't work for React Native and doesn't let us
|
||||
// do the portal feature.
|
||||
} while (inst && inst.tag !== HostComponent);
|
||||
if (inst) {
|
||||
return inst;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the lowest common ancestor of A and B, or null if they are in
|
||||
* different trees.
|
||||
*/
|
||||
function getLowestCommonAncestor(instA: Fiber, instB: Fiber): Fiber | null {
|
||||
let nodeA = instA;
|
||||
let nodeB = instB;
|
||||
let depthA = 0;
|
||||
for (let tempA = nodeA; tempA; tempA = getParent(tempA)) {
|
||||
depthA++;
|
||||
}
|
||||
let depthB = 0;
|
||||
for (let tempB = nodeB; tempB; tempB = getParent(tempB)) {
|
||||
depthB++;
|
||||
}
|
||||
|
||||
// If A is deeper, crawl up.
|
||||
while (depthA - depthB > 0) {
|
||||
nodeA = getParent(nodeA);
|
||||
depthA--;
|
||||
}
|
||||
|
||||
// If B is deeper, crawl up.
|
||||
while (depthB - depthA > 0) {
|
||||
nodeB = getParent(nodeB);
|
||||
depthB--;
|
||||
}
|
||||
|
||||
// Walk in lockstep until we find a match.
|
||||
let depth = depthA;
|
||||
while (depth--) {
|
||||
if (nodeA === nodeB || (nodeB !== null && nodeA === nodeB.alternate)) {
|
||||
return nodeA;
|
||||
}
|
||||
nodeA = getParent(nodeA);
|
||||
nodeB = getParent(nodeB);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function accumulateEnterLeaveListenersForEvent(
|
||||
event: ReactSyntheticEvent,
|
||||
target: Fiber,
|
||||
common: Fiber | null,
|
||||
capture: boolean,
|
||||
): void {
|
||||
const registrationName = event.dispatchConfig.registrationName;
|
||||
if (registrationName === undefined) {
|
||||
return;
|
||||
}
|
||||
const dispatchListeners = [];
|
||||
const dispatchInstances = [];
|
||||
|
||||
let node = target;
|
||||
while (node !== null) {
|
||||
if (node === common) {
|
||||
break;
|
||||
}
|
||||
const alternate = node.alternate;
|
||||
if (alternate !== null && alternate === common) {
|
||||
break;
|
||||
}
|
||||
if (node.tag === HostComponent) {
|
||||
if (capture) {
|
||||
const captureListener = getListener(node, registrationName);
|
||||
if (captureListener != null) {
|
||||
// Capture listeners/instances should go at the start, so we
|
||||
// unshift them to the start of the array.
|
||||
dispatchListeners.unshift(captureListener);
|
||||
dispatchInstances.unshift(node);
|
||||
}
|
||||
} else {
|
||||
const bubbleListener = getListener(node, registrationName);
|
||||
if (bubbleListener != null) {
|
||||
// Bubble listeners/instances should go at the end, so we
|
||||
// push them to the end of the array.
|
||||
dispatchListeners.push(bubbleListener);
|
||||
dispatchInstances.push(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
node = node.return;
|
||||
}
|
||||
// To prevent allocation to the event unless we actually
|
||||
// have listeners we check the length of one of the arrays.
|
||||
if (dispatchListeners.length > 0) {
|
||||
event._dispatchListeners = dispatchListeners;
|
||||
event._dispatchInstances = dispatchInstances;
|
||||
}
|
||||
}
|
||||
|
||||
export default function accumulateEnterLeaveListeners(
|
||||
leaveEvent: ReactSyntheticEvent,
|
||||
enterEvent: ReactSyntheticEvent,
|
||||
from: Fiber | null,
|
||||
to: Fiber | null,
|
||||
): void {
|
||||
const common = from && to ? getLowestCommonAncestor(from, to) : null;
|
||||
|
||||
if (from !== null) {
|
||||
accumulateEnterLeaveListenersForEvent(leaveEvent, from, common, false);
|
||||
}
|
||||
if (to !== null) {
|
||||
accumulateEnterLeaveListenersForEvent(enterEvent, to, common, true);
|
||||
}
|
||||
}
|
||||
|
|
@ -23,7 +23,6 @@ import {PLUGIN_EVENT_SYSTEM} from 'legacy-events/EventSystemFlags';
|
|||
import act from './ReactTestUtilsAct';
|
||||
import forEachAccumulated from 'legacy-events/forEachAccumulated';
|
||||
import accumulateInto from 'legacy-events/accumulateInto';
|
||||
import {traverseTwoPhase} from 'react-reconciler/src/ReactTreeTraversal';
|
||||
|
||||
const {findDOMNode} = ReactDOM;
|
||||
// Keep in sync with ReactDOMUnstableNativeDependencies.js
|
||||
|
|
@ -390,6 +389,39 @@ function isInteractive(tag) {
|
|||
);
|
||||
}
|
||||
|
||||
function getParent(inst) {
|
||||
do {
|
||||
inst = inst.return;
|
||||
// TODO: If this is a HostRoot we might want to bail out.
|
||||
// That is depending on if we want nested subtrees (layers) to bubble
|
||||
// events to their parent. We could also go through parentNode on the
|
||||
// host node but that wouldn't work for React Native and doesn't let us
|
||||
// do the portal feature.
|
||||
} while (inst && inst.tag !== HostComponent);
|
||||
if (inst) {
|
||||
return inst;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates the traversal of a two-phase, capture/bubble event dispatch.
|
||||
*/
|
||||
export function traverseTwoPhase(inst, fn, arg) {
|
||||
const path = [];
|
||||
while (inst) {
|
||||
path.push(inst);
|
||||
inst = getParent(inst);
|
||||
}
|
||||
let i;
|
||||
for (i = path.length; i-- > 0; ) {
|
||||
fn(path[i], 'captured', arg);
|
||||
}
|
||||
for (i = 0; i < path.length; i++) {
|
||||
fn(path[i], 'bubbled', arg);
|
||||
}
|
||||
}
|
||||
|
||||
function shouldPreventMouseEvent(name, type, props) {
|
||||
switch (name) {
|
||||
case 'onClick':
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@
|
|||
|
||||
import type {AnyNativeEvent} from 'legacy-events/PluginModuleType';
|
||||
import type {EventSystemFlags} from 'legacy-events/EventSystemFlags';
|
||||
import {accumulateDirectDispatches} from 'legacy-events/EventPropagators';
|
||||
import type {TopLevelType} from 'legacy-events/TopLevelEventTypes';
|
||||
import SyntheticEvent from 'legacy-events/SyntheticEvent';
|
||||
import invariant from 'shared/invariant';
|
||||
|
|
@ -18,8 +17,8 @@ import invariant from 'shared/invariant';
|
|||
import {ReactNativeViewConfigRegistry} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';
|
||||
import accumulateInto from 'legacy-events/accumulateInto';
|
||||
import getListener from 'legacy-events/getListener';
|
||||
import {traverseTwoPhase} from 'react-reconciler/src/ReactTreeTraversal';
|
||||
import forEachAccumulated from 'legacy-events/forEachAccumulated';
|
||||
import {HostComponent} from 'react-reconciler/src/ReactWorkTags';
|
||||
|
||||
const {
|
||||
customBubblingEventTypes,
|
||||
|
|
@ -51,6 +50,39 @@ function accumulateDirectionalDispatches(inst, phase, event) {
|
|||
}
|
||||
}
|
||||
|
||||
function getParent(inst) {
|
||||
do {
|
||||
inst = inst.return;
|
||||
// TODO: If this is a HostRoot we might want to bail out.
|
||||
// That is depending on if we want nested subtrees (layers) to bubble
|
||||
// events to their parent. We could also go through parentNode on the
|
||||
// host node but that wouldn't work for React Native and doesn't let us
|
||||
// do the portal feature.
|
||||
} while (inst && inst.tag !== HostComponent);
|
||||
if (inst) {
|
||||
return inst;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates the traversal of a two-phase, capture/bubble event dispatch.
|
||||
*/
|
||||
export function traverseTwoPhase(inst: Object, fn: Function, arg: Function) {
|
||||
const path = [];
|
||||
while (inst) {
|
||||
path.push(inst);
|
||||
inst = getParent(inst);
|
||||
}
|
||||
let i;
|
||||
for (i = path.length; i-- > 0; ) {
|
||||
fn(path[i], 'captured', arg);
|
||||
}
|
||||
for (i = 0; i < path.length; i++) {
|
||||
fn(path[i], 'bubbled', arg);
|
||||
}
|
||||
}
|
||||
|
||||
function accumulateTwoPhaseDispatchesSingle(event) {
|
||||
if (event && event.dispatchConfig.phasedRegistrationNames) {
|
||||
traverseTwoPhase(event._targetInst, accumulateDirectionalDispatches, event);
|
||||
|
|
@ -60,6 +92,45 @@ function accumulateTwoPhaseDispatchesSingle(event) {
|
|||
function accumulateTwoPhaseDispatches(events) {
|
||||
forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Accumulates without regard to direction, does not look for phased
|
||||
* registration names. Same as `accumulateDirectDispatchesSingle` but without
|
||||
* requiring that the `dispatchMarker` be the same as the dispatched ID.
|
||||
*/
|
||||
function accumulateDispatches(
|
||||
inst: Object,
|
||||
ignoredDirection: ?boolean,
|
||||
event: Object,
|
||||
): void {
|
||||
if (inst && event && event.dispatchConfig.registrationName) {
|
||||
const registrationName = event.dispatchConfig.registrationName;
|
||||
const listener = getListener(inst, registrationName);
|
||||
if (listener) {
|
||||
event._dispatchListeners = accumulateInto(
|
||||
event._dispatchListeners,
|
||||
listener,
|
||||
);
|
||||
event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accumulates dispatches on an `SyntheticEvent`, but only for the
|
||||
* `dispatchMarker`.
|
||||
* @param {SyntheticEvent} event
|
||||
*/
|
||||
function accumulateDirectDispatchesSingle(event: Object) {
|
||||
if (event && event.dispatchConfig.registrationName) {
|
||||
accumulateDispatches(event._targetInst, null, event);
|
||||
}
|
||||
}
|
||||
|
||||
function accumulateDirectDispatches(events: ?(Array<Object> | Object)) {
|
||||
forEachAccumulated(events, accumulateDirectDispatchesSingle);
|
||||
}
|
||||
|
||||
// End of inline
|
||||
type PropagationPhases = 'bubbled' | 'captured';
|
||||
|
||||
|
|
|
|||
146
packages/react-reconciler/src/ReactTreeTraversal.js
vendored
146
packages/react-reconciler/src/ReactTreeTraversal.js
vendored
|
|
@ -1,146 +0,0 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {HostComponent} from './ReactWorkTags';
|
||||
|
||||
function getParent(inst) {
|
||||
do {
|
||||
inst = inst.return;
|
||||
// TODO: If this is a HostRoot we might want to bail out.
|
||||
// That is depending on if we want nested subtrees (layers) to bubble
|
||||
// events to their parent. We could also go through parentNode on the
|
||||
// host node but that wouldn't work for React Native and doesn't let us
|
||||
// do the portal feature.
|
||||
} while (inst && inst.tag !== HostComponent);
|
||||
if (inst) {
|
||||
return inst;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the lowest common ancestor of A and B, or null if they are in
|
||||
* different trees.
|
||||
*/
|
||||
export function getLowestCommonAncestor(instA, instB) {
|
||||
let depthA = 0;
|
||||
for (let tempA = instA; tempA; tempA = getParent(tempA)) {
|
||||
depthA++;
|
||||
}
|
||||
let depthB = 0;
|
||||
for (let tempB = instB; tempB; tempB = getParent(tempB)) {
|
||||
depthB++;
|
||||
}
|
||||
|
||||
// If A is deeper, crawl up.
|
||||
while (depthA - depthB > 0) {
|
||||
instA = getParent(instA);
|
||||
depthA--;
|
||||
}
|
||||
|
||||
// If B is deeper, crawl up.
|
||||
while (depthB - depthA > 0) {
|
||||
instB = getParent(instB);
|
||||
depthB--;
|
||||
}
|
||||
|
||||
// Walk in lockstep until we find a match.
|
||||
let depth = depthA;
|
||||
while (depth--) {
|
||||
if (instA === instB || instA === instB.alternate) {
|
||||
return instA;
|
||||
}
|
||||
instA = getParent(instA);
|
||||
instB = getParent(instB);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if A is an ancestor of B.
|
||||
*/
|
||||
export function isAncestor(instA, instB) {
|
||||
while (instB) {
|
||||
if (instA === instB || instA === instB.alternate) {
|
||||
return true;
|
||||
}
|
||||
instB = getParent(instB);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the parent instance of the passed-in instance.
|
||||
*/
|
||||
export function getParentInstance(inst) {
|
||||
return getParent(inst);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates the traversal of a two-phase, capture/bubble event dispatch.
|
||||
*/
|
||||
export function traverseTwoPhase(inst, fn, arg) {
|
||||
const path = [];
|
||||
while (inst) {
|
||||
path.push(inst);
|
||||
inst = getParent(inst);
|
||||
}
|
||||
let i;
|
||||
for (i = path.length; i-- > 0; ) {
|
||||
fn(path[i], 'captured', arg);
|
||||
}
|
||||
for (i = 0; i < path.length; i++) {
|
||||
fn(path[i], 'bubbled', arg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverses the ID hierarchy and invokes the supplied `cb` on any IDs that
|
||||
* should would receive a `mouseEnter` or `mouseLeave` event.
|
||||
*
|
||||
* Does not invoke the callback on the nearest common ancestor because nothing
|
||||
* "entered" or "left" that element.
|
||||
*/
|
||||
export function traverseEnterLeave(from, to, fn, argFrom, argTo) {
|
||||
const common = from && to ? getLowestCommonAncestor(from, to) : null;
|
||||
const pathFrom = [];
|
||||
while (true) {
|
||||
if (!from) {
|
||||
break;
|
||||
}
|
||||
if (from === common) {
|
||||
break;
|
||||
}
|
||||
const alternate = from.alternate;
|
||||
if (alternate !== null && alternate === common) {
|
||||
break;
|
||||
}
|
||||
pathFrom.push(from);
|
||||
from = getParent(from);
|
||||
}
|
||||
const pathTo = [];
|
||||
while (true) {
|
||||
if (!to) {
|
||||
break;
|
||||
}
|
||||
if (to === common) {
|
||||
break;
|
||||
}
|
||||
const alternate = to.alternate;
|
||||
if (alternate !== null && alternate === common) {
|
||||
break;
|
||||
}
|
||||
pathTo.push(to);
|
||||
to = getParent(to);
|
||||
}
|
||||
for (let i = 0; i < pathFrom.length; i++) {
|
||||
fn(pathFrom[i], 'bubbled', argFrom);
|
||||
}
|
||||
for (let i = pathTo.length; i-- > 0; ) {
|
||||
fn(pathTo[i], 'captured', argTo);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user