Add part of the event responder system for experimental event API (#15179)

* Add part of the event responder system
This commit is contained in:
Dominic Gannaway 2019-03-26 16:55:25 -07:00 committed by GitHub
parent d03ac4b231
commit 80f8b0d512
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 593 additions and 107 deletions

View File

@ -0,0 +1,66 @@
/**
* 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 invariant from 'shared/invariant';
import {rethrowCaughtError} from 'shared/ReactErrorUtils';
import type {ReactSyntheticEvent} from './ReactSyntheticEventType';
import accumulateInto from './accumulateInto';
import forEachAccumulated from './forEachAccumulated';
import {executeDispatchesInOrder} from './EventPluginUtils';
/**
* Internal queue of events that have accumulated their dispatches and are
* waiting to have their dispatches executed.
*/
let eventQueue: ?(Array<ReactSyntheticEvent> | ReactSyntheticEvent) = null;
/**
* Dispatches an event and releases it back into the pool, unless persistent.
*
* @param {?object} event Synthetic event to be dispatched.
* @private
*/
const executeDispatchesAndRelease = function(event: ReactSyntheticEvent) {
if (event) {
executeDispatchesInOrder(event);
if (!event.isPersistent()) {
event.constructor.release(event);
}
}
};
const executeDispatchesAndReleaseTopLevel = function(e) {
return executeDispatchesAndRelease(e);
};
export function runEventsInBatch(
events: Array<ReactSyntheticEvent> | ReactSyntheticEvent | null,
) {
if (events !== null) {
eventQueue = accumulateInto(eventQueue, events);
}
// Set `eventQueue` to null before processing it so that we can tell if more
// events get enqueued while processing.
const processingEventQueue = eventQueue;
eventQueue = null;
if (!processingEventQueue) {
return;
}
forEachAccumulated(processingEventQueue, executeDispatchesAndReleaseTopLevel);
invariant(
!eventQueue,
'processEventQueue(): Additional events were enqueued while processing ' +
'an event queue. Support for this has not yet been implemented.',
);
// This would be a good time to rethrow if any of the event handlers threw.
rethrowCaughtError();
}

View File

@ -6,7 +6,6 @@
* @flow
*/
import {rethrowCaughtError} from 'shared/ReactErrorUtils';
import invariant from 'shared/invariant';
import {
@ -14,12 +13,9 @@ import {
injectEventPluginsByName,
plugins,
} from './EventPluginRegistry';
import {
executeDispatchesInOrder,
getFiberCurrentPropsFromNode,
} from './EventPluginUtils';
import {getFiberCurrentPropsFromNode} from './EventPluginUtils';
import accumulateInto from './accumulateInto';
import forEachAccumulated from './forEachAccumulated';
import {runEventsInBatch} from './EventBatching';
import type {PluginModule} from './PluginModuleType';
import type {ReactSyntheticEvent} from './ReactSyntheticEventType';
@ -27,31 +23,6 @@ import type {Fiber} from 'react-reconciler/src/ReactFiber';
import type {AnyNativeEvent} from './PluginModuleType';
import type {TopLevelType} from './TopLevelEventTypes';
/**
* Internal queue of events that have accumulated their dispatches and are
* waiting to have their dispatches executed.
*/
let eventQueue: ?(Array<ReactSyntheticEvent> | ReactSyntheticEvent) = null;
/**
* Dispatches an event and releases it back into the pool, unless persistent.
*
* @param {?object} event Synthetic event to be dispatched.
* @private
*/
const executeDispatchesAndRelease = function(event: ReactSyntheticEvent) {
if (event) {
executeDispatchesInOrder(event);
if (!event.isPersistent()) {
event.constructor.release(event);
}
}
};
const executeDispatchesAndReleaseTopLevel = function(e) {
return executeDispatchesAndRelease(e);
};
function isInteractive(tag) {
return (
tag === 'button' ||
@ -158,7 +129,7 @@ export function getListener(inst: Fiber, registrationName: string) {
* @return {*} An accumulation of synthetic events.
* @internal
*/
function extractEvents(
function extractPluginEvents(
topLevelType: TopLevelType,
targetInst: null | Fiber,
nativeEvent: AnyNativeEvent,
@ -183,39 +154,13 @@ function extractEvents(
return events;
}
export function runEventsInBatch(
events: Array<ReactSyntheticEvent> | ReactSyntheticEvent | null,
) {
if (events !== null) {
eventQueue = accumulateInto(eventQueue, events);
}
// Set `eventQueue` to null before processing it so that we can tell if more
// events get enqueued while processing.
const processingEventQueue = eventQueue;
eventQueue = null;
if (!processingEventQueue) {
return;
}
forEachAccumulated(processingEventQueue, executeDispatchesAndReleaseTopLevel);
invariant(
!eventQueue,
'processEventQueue(): Additional events were enqueued while processing ' +
'an event queue. Support for this has not yet been implemented.',
);
// This would be a good time to rethrow if any of the event handlers threw.
rethrowCaughtError();
}
export function runExtractedEventsInBatch(
export function runExtractedPluginEventsInBatch(
topLevelType: TopLevelType,
targetInst: null | Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: EventTarget,
) {
const events = extractEvents(
const events = extractPluginEvents(
topLevelType,
targetInst,
nativeEvent,

View File

@ -11,7 +11,7 @@
const {HostComponent} = require('shared/ReactWorkTags');
let EventPluginHub;
let EventBatching;
let EventPluginUtils;
let ResponderEventPlugin;
@ -321,7 +321,7 @@ const run = function(config, hierarchyConfig, nativeEventConfig) {
// At this point the negotiation events have been dispatched as part of the
// extraction process, but not the side effectful events. Below, we dispatch
// side effectful events.
EventPluginHub.runEventsInBatch(extractedEvents);
EventBatching.runEventsInBatch(extractedEvents);
// Ensure that every event that declared an `order`, was actually dispatched.
expect('number of events dispatched:' + runData.dispatchCount).toBe(
@ -403,7 +403,7 @@ describe('ResponderEventPlugin', () => {
jest.resetModules();
const ReactDOMUnstableNativeDependencies = require('react-dom/unstable-native-dependencies');
EventPluginHub = require('events/EventPluginHub');
EventBatching = require('events/EventBatching');
EventPluginUtils = require('events/EventPluginUtils');
ResponderEventPlugin =
ReactDOMUnstableNativeDependencies.ResponderEventPlugin;

View File

@ -44,10 +44,8 @@ import {
enqueueStateRestore,
restoreStateIfNeeded,
} from 'events/ReactControlledComponent';
import {
injection as EventPluginHubInjection,
runEventsInBatch,
} from 'events/EventPluginHub';
import {injection as EventPluginHubInjection} from 'events/EventPluginHub';
import {runEventsInBatch} from 'events/EventBatching';
import {eventNameDispatchConfigs} from 'events/EventPluginRegistry';
import {
accumulateTwoPhaseDispatches,

View File

@ -862,8 +862,10 @@ export function handleEventComponent(
rootContainerInstance: Container,
internalInstanceHandle: Object,
): void {
const rootElement = rootContainerInstance.ownerDocument;
listenToEventResponderEvents(eventResponder, rootElement);
if (enableEventAPI) {
const rootElement = rootContainerInstance.ownerDocument;
listenToEventResponderEvents(eventResponder, rootElement);
}
}
export function handleEventTarget(
@ -871,20 +873,22 @@ export function handleEventTarget(
props: Props,
internalInstanceHandle: Object,
): void {
// Touch target hit slop handling
if (type === REACT_EVENT_TARGET_TOUCH_HIT) {
// Validates that there is a single element
const element = getElementFromTouchHitTarget(internalInstanceHandle);
if (element !== null) {
// We update the event target state node to be that of the element.
// We can then diff this entry to determine if we need to add the
// hit slop element, or change the dimensions of the hit slop.
const lastElement = internalInstanceHandle.stateNode;
if (lastElement !== element) {
internalInstanceHandle.stateNode = element;
// TODO: Create the hit slop element and attach it to the element
} else {
// TODO: Diff the left, top, right, bottom props
if (enableEventAPI) {
// Touch target hit slop handling
if (type === REACT_EVENT_TARGET_TOUCH_HIT) {
// Validates that there is a single element
const element = getElementFromTouchHitTarget(internalInstanceHandle);
if (element !== null) {
// We update the event target state node to be that of the element.
// We can then diff this entry to determine if we need to add the
// hit slop element, or change the dimensions of the hit slop.
const lastElement = internalInstanceHandle.stateNode;
if (lastElement !== element) {
internalInstanceHandle.stateNode = element;
// TODO: Create the hit slop element and attach it to the element
} else {
// TODO: Diff the left, top, right, bottom props
}
}
}
}

View File

@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {runEventsInBatch} from 'events/EventPluginHub';
import {runEventsInBatch} from 'events/EventBatching';
import {accumulateTwoPhaseDispatches} from 'events/EventPropagators';
import {enqueueStateRestore} from 'events/ReactControlledComponent';
import {batchedUpdates} from 'events/ReactGenericBatching';

View File

@ -0,0 +1,195 @@
/**
* 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 EventSystemFlags,
IS_PASSIVE,
PASSIVE_NOT_SUPPORTED,
} from 'events/EventSystemFlags';
import type {AnyNativeEvent} from 'events/PluginModuleType';
import {EventComponent} from 'shared/ReactWorkTags';
import type {ReactEventResponder} from 'shared/ReactTypes';
import warning from 'shared/warning';
import type {DOMTopLevelEventType} from 'events/TopLevelEventTypes';
import SyntheticEvent from 'events/SyntheticEvent';
import {runEventsInBatch} from 'events/EventBatching';
import {interactiveUpdates} from 'events/ReactGenericBatching';
import type {Fiber} from 'react-reconciler/src/ReactFiber';
import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree';
// Event responders provide us an array of target event types.
// To ensure we fire the right responders for given events, we check
// if the incoming event type is actually relevant for an event
// responder. Instead of doing an O(n) lookup on the event responder
// target event types array each time, we instead create a Set for
// faster O(1) lookups.
export const eventResponderValidEventTypes: Map<
ReactEventResponder,
Set<DOMTopLevelEventType>,
> = new Map();
type EventListener = (event: SyntheticEvent) => void;
// TODO add context methods for dispatching events
function DOMEventResponderContext(
topLevelType: DOMTopLevelEventType,
nativeEvent: AnyNativeEvent,
nativeEventTarget: EventTarget,
eventSystemFlags: EventSystemFlags,
) {
this.event = nativeEvent;
this.eventType = topLevelType;
this.eventTarget = nativeEventTarget;
this._flags = eventSystemFlags;
this._fiber = null;
this._responder = null;
this._discreteEvents = null;
this._nonDiscreteEvents = null;
}
DOMEventResponderContext.prototype.isPassive = function(): boolean {
return (this._flags & IS_PASSIVE) !== 0;
};
DOMEventResponderContext.prototype.isPassiveSupported = function(): boolean {
return (this._flags & PASSIVE_NOT_SUPPORTED) === 0;
};
function copyEventProperties(eventData, syntheticEvent) {
for (let propName in eventData) {
syntheticEvent[propName] = eventData[propName];
}
}
DOMEventResponderContext.prototype.dispatchEvent = function(
eventName: string,
eventListener: EventListener,
eventTarget: AnyNativeEvent,
discrete: boolean,
extraProperties?: Object,
): void {
const eventTargetFiber = getClosestInstanceFromNode(eventTarget);
const syntheticEvent = SyntheticEvent.getPooled(
null,
eventTargetFiber,
this.event,
eventTarget,
);
if (extraProperties !== undefined) {
copyEventProperties(extraProperties, syntheticEvent);
}
syntheticEvent.type = eventName;
syntheticEvent._dispatchInstances = [eventTargetFiber];
syntheticEvent._dispatchListeners = [eventListener];
let events;
if (discrete) {
events = this._discreteEvents;
if (events === null) {
events = this._discreteEvents = [];
}
} else {
events = this._nonDiscreteEvents;
if (events === null) {
events = this._nonDiscreteEvents = [];
}
}
events.push(syntheticEvent);
};
DOMEventResponderContext.prototype._runEventsInBatch = function(): void {
if (this._discreteEvents !== null) {
interactiveUpdates(() => {
runEventsInBatch(this._discreteEvents);
});
}
if (this._nonDiscreteEvents !== null) {
runEventsInBatch(this._nonDiscreteEvents);
}
};
function createValidEventTypeSet(targetEventTypes): Set<DOMTopLevelEventType> {
const eventTypeSet = new Set();
// Go through each target event type of the event responder
for (let i = 0, length = targetEventTypes.length; i < length; ++i) {
const targetEventType = targetEventTypes[i];
if (typeof targetEventType === 'string') {
eventTypeSet.add(((targetEventType: any): DOMTopLevelEventType));
} else {
if (__DEV__) {
warning(
typeof targetEventType === 'object' && targetEventType !== null,
'Event Responder: invalid entry in targetEventTypes array. ' +
'Entry must be string or an object. Instead, got %s.',
targetEventType,
);
}
const targetEventConfigObject = ((targetEventType: any): {
name: DOMTopLevelEventType,
passive?: boolean,
capture?: boolean,
});
eventTypeSet.add(targetEventConfigObject.name);
}
}
return eventTypeSet;
}
function handleTopLevelType(
topLevelType: DOMTopLevelEventType,
fiber: Fiber,
context: Object,
): void {
const responder: ReactEventResponder = fiber.type.responder;
let {props, state} = fiber.stateNode;
let validEventTypesForResponder = eventResponderValidEventTypes.get(
responder,
);
if (validEventTypesForResponder === undefined) {
validEventTypesForResponder = createValidEventTypeSet(
responder.targetEventTypes,
);
eventResponderValidEventTypes.set(responder, validEventTypesForResponder);
}
if (!validEventTypesForResponder.has(topLevelType)) {
return;
}
if (state === null && responder.createInitialState !== undefined) {
state = fiber.stateNode.state = responder.createInitialState(props);
}
context._fiber = fiber;
context._responder = responder;
responder.handleEvent(context, props, state);
}
export function runResponderEventsInBatch(
topLevelType: DOMTopLevelEventType,
targetFiber: Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: EventTarget,
eventSystemFlags: EventSystemFlags,
): void {
const context = new DOMEventResponderContext(
topLevelType,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
);
let node = targetFiber;
// Traverse up the fiber tree till we find event component fibers.
while (node !== null) {
if (node.tag === EventComponent) {
handleTopLevelType(topLevelType, node, context);
}
node = node.return;
}
context._runEventsInBatch();
}

View File

@ -12,7 +12,8 @@ import type {Fiber} from 'react-reconciler/src/ReactFiber';
import type {DOMTopLevelEventType} from 'events/TopLevelEventTypes';
import {batchedUpdates, interactiveUpdates} from 'events/ReactGenericBatching';
import {runExtractedEventsInBatch} from 'events/EventPluginHub';
import {runExtractedPluginEventsInBatch} from 'events/EventPluginHub';
import {runResponderEventsInBatch} from '../events/DOMEventResponderSystem';
import {isFiberMounted} from 'react-reconciler/reflection';
import {HostRoot} from 'shared/ReactWorkTags';
import {
@ -130,16 +131,27 @@ function handleTopLevel(bookKeeping: BookKeepingInstance) {
for (let i = 0; i < bookKeeping.ancestors.length; i++) {
targetInst = bookKeeping.ancestors[i];
if (bookKeeping.eventSystemFlags === PLUGIN_EVENT_SYSTEM) {
runExtractedEventsInBatch(
((bookKeeping.topLevelType: any): DOMTopLevelEventType),
const eventSystemFlags = bookKeeping.eventSystemFlags;
const eventTarget = getEventTarget(bookKeeping.nativeEvent);
const topLevelType = ((bookKeeping.topLevelType: any): DOMTopLevelEventType);
const nativeEvent = ((bookKeeping.nativeEvent: any): AnyNativeEvent);
if (eventSystemFlags === PLUGIN_EVENT_SYSTEM) {
runExtractedPluginEventsInBatch(
topLevelType,
targetInst,
((bookKeeping.nativeEvent: any): AnyNativeEvent),
getEventTarget(bookKeeping.nativeEvent),
nativeEvent,
eventTarget,
);
} else if (enableEventAPI && targetInst !== null) {
// Responder event system (experimental event API)
runResponderEventsInBatch(
topLevelType,
targetInst,
nativeEvent,
eventTarget,
eventSystemFlags,
);
} else {
// RESPONDER_EVENT_SYSTEM
// TODO: Add implementation
}
}
}
@ -176,9 +188,6 @@ export function trapEventForResponderEventSystem(
passive: boolean,
): void {
if (enableEventAPI) {
const dispatch = isInteractiveTopLevelEventType(topLevelType)
? dispatchInteractiveEvent
: dispatchEvent;
const rawEventName = getRawEventName(topLevelType);
let eventFlags = RESPONDER_EVENT_SYSTEM;
@ -198,7 +207,7 @@ export function trapEventForResponderEventSystem(
eventFlags |= IS_ACTIVE;
}
// Check if interactive and wrap in interactiveUpdates
const listener = dispatch.bind(null, topLevelType, eventFlags);
const listener = dispatchEvent.bind(null, topLevelType, eventFlags);
addEventListener(element, rawEventName, listener, {
capture,
passive,

View File

@ -0,0 +1,259 @@
/**
* 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.
*
* @emails react-core
*/
'use strict';
let React;
let ReactFeatureFlags;
let ReactDOM;
function createReactEventComponent(targetEventTypes, handleEvent) {
const testEventResponder = {
targetEventTypes,
handleEvent,
};
return {
$$typeof: Symbol.for('react.event_component'),
props: null,
responder: testEventResponder,
};
}
function dispatchClickEvent(element) {
const clickEvent = document.createEvent('Event');
clickEvent.initEvent('click', true, true);
element.dispatchEvent(clickEvent);
}
// This is a new feature in Fiber so I put it in its own test file. It could
// probably move to one of the other test files once it is official.
describe('DOMEventResponderSystem', () => {
let container;
beforeEach(() => {
jest.resetModules();
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.enableEventAPI = true;
React = require('react');
ReactDOM = require('react-dom');
container = document.createElement('div');
document.body.appendChild(container);
});
afterEach(() => {
document.body.removeChild(container);
container = null;
});
it('the event responder handleEvent() function should fire on click event', () => {
let eventResponderFiredCount = 0;
let eventLog = [];
const buttonRef = React.createRef();
const ClickEventComponent = createReactEventComponent(
['click'],
(context, props) => {
eventResponderFiredCount++;
eventLog.push({
name: context.eventType,
passive: context.isPassive(),
passiveSupported: context.isPassiveSupported(),
});
},
);
const Test = () => (
<ClickEventComponent>
<button ref={buttonRef}>Click me!</button>
</ClickEventComponent>
);
ReactDOM.render(<Test />, container);
expect(container.innerHTML).toBe('<button>Click me!</button>');
// Clicking the button should trigger the event responder handleEvent()
let buttonElement = buttonRef.current;
dispatchClickEvent(buttonElement);
expect(eventResponderFiredCount).toBe(1);
expect(eventLog.length).toBe(1);
// JSDOM does not support passive events, so this will be false
expect(eventLog[0]).toEqual({
name: 'click',
passive: false,
passiveSupported: false,
});
// Unmounting the container and clicking should not increment anything
ReactDOM.render(null, container);
dispatchClickEvent(buttonElement);
expect(eventResponderFiredCount).toBe(1);
// Re-rendering the container and clicking should increase the counter again
ReactDOM.render(<Test />, container);
buttonElement = buttonRef.current;
dispatchClickEvent(buttonElement);
expect(eventResponderFiredCount).toBe(2);
});
it('the event responder handleEvent() function should fire on click event (passive events forced)', () => {
// JSDOM does not support passive events, so this manually overrides the value to be true
const checkPassiveEvents = require('react-dom/src/events/checkPassiveEvents');
checkPassiveEvents.passiveBrowserEventsSupported = true;
let eventLog = [];
const buttonRef = React.createRef();
const ClickEventComponent = createReactEventComponent(
['click'],
(context, props) => {
eventLog.push({
name: context.eventType,
passive: context.isPassive(),
passiveSupported: context.isPassiveSupported(),
});
},
);
const Test = () => (
<ClickEventComponent>
<button ref={buttonRef}>Click me!</button>
</ClickEventComponent>
);
ReactDOM.render(<Test />, container);
// Clicking the button should trigger the event responder handleEvent()
let buttonElement = buttonRef.current;
dispatchClickEvent(buttonElement);
expect(eventLog.length).toBe(1);
expect(eventLog[0]).toEqual({
name: 'click',
passive: true,
passiveSupported: true,
});
});
it('nested event responders and their handleEvent() function should fire multiple times', () => {
let eventResponderFiredCount = 0;
let eventLog = [];
const buttonRef = React.createRef();
const ClickEventComponent = createReactEventComponent(
['click'],
(context, props) => {
eventResponderFiredCount++;
eventLog.push({
name: context.eventType,
passive: context.isPassive(),
passiveSupported: context.isPassiveSupported(),
});
},
);
const Test = () => (
<ClickEventComponent>
<ClickEventComponent>
<button ref={buttonRef}>Click me!</button>
</ClickEventComponent>
</ClickEventComponent>
);
ReactDOM.render(<Test />, container);
// Clicking the button should trigger the event responder handleEvent()
let buttonElement = buttonRef.current;
dispatchClickEvent(buttonElement);
expect(eventResponderFiredCount).toBe(2);
expect(eventLog.length).toBe(2);
// JSDOM does not support passive events, so this will be false
expect(eventLog[0]).toEqual({
name: 'click',
passive: false,
passiveSupported: false,
});
expect(eventLog[1]).toEqual({
name: 'click',
passive: false,
passiveSupported: false,
});
});
it('nested event responders and their handleEvent() should fire in the correct order', () => {
let eventLog = [];
const buttonRef = React.createRef();
const ClickEventComponentA = createReactEventComponent(
['click'],
(context, props) => {
eventLog.push('A');
},
);
const ClickEventComponentB = createReactEventComponent(
['click'],
(context, props) => {
eventLog.push('B');
},
);
const Test = () => (
<ClickEventComponentA>
<ClickEventComponentB>
<button ref={buttonRef}>Click me!</button>
</ClickEventComponentB>
</ClickEventComponentA>
);
ReactDOM.render(<Test />, container);
// Clicking the button should trigger the event responder handleEvent()
let buttonElement = buttonRef.current;
dispatchClickEvent(buttonElement);
expect(eventLog).toEqual(['B', 'A']);
});
it('custom event dispatching for click -> magicClick works', () => {
let eventLog = [];
const buttonRef = React.createRef();
const ClickEventComponent = createReactEventComponent(
['click'],
(context, props) => {
if (props.onMagicClick) {
context.dispatchEvent(
'magicclick',
props.onMagicClick,
context.eventTarget,
false,
);
}
},
);
function handleMagicEvent(e) {
eventLog.push('magic event fired', e.type);
}
const Test = () => (
<ClickEventComponent onMagicClick={handleMagicEvent}>
<button ref={buttonRef}>Click me!</button>
</ClickEventComponent>
);
ReactDOM.render(<Test />, container);
// Clicking the button should trigger the event responder handleEvent()
let buttonElement = buttonRef.current;
dispatchClickEvent(buttonElement);
expect(eventLog).toEqual(['magic event fired', 'magicclick']);
});
});

View File

@ -49,10 +49,9 @@ import {
enqueueStateRestore,
restoreStateIfNeeded,
} from 'events/ReactControlledComponent';
import {
injection as EventPluginHubInjection,
runEventsInBatch,
} from 'events/EventPluginHub';
import {injection as EventPluginHubInjection} from 'events/EventPluginHub';
import {runEventsInBatch} from 'events/EventBatching';
import {eventNameDispatchConfigs} from 'events/EventPluginRegistry';
import {
accumulateTwoPhaseDispatches,

View File

@ -9,7 +9,10 @@
import type {Fiber} from 'react-reconciler/src/ReactFiber';
import {getListener, runExtractedEventsInBatch} from 'events/EventPluginHub';
import {
getListener,
runExtractedPluginEventsInBatch,
} from 'events/EventPluginHub';
import {registrationNameModules} from 'events/EventPluginRegistry';
import {batchedUpdates} from 'events/ReactGenericBatching';
@ -25,7 +28,7 @@ export function dispatchEvent(
) {
const targetFiber = (target: null | Fiber);
batchedUpdates(function() {
runExtractedEventsInBatch(
runExtractedPluginEventsInBatch(
topLevelType,
targetFiber,
nativeEvent,

View File

@ -7,7 +7,10 @@
* @flow
*/
import {getListener, runExtractedEventsInBatch} from 'events/EventPluginHub';
import {
getListener,
runExtractedPluginEventsInBatch,
} from 'events/EventPluginHub';
import {registrationNameModules} from 'events/EventPluginRegistry';
import {batchedUpdates} from 'events/ReactGenericBatching';
import warningWithoutStack from 'shared/warningWithoutStack';
@ -95,7 +98,7 @@ function _receiveRootNodeIDEvent(
const nativeEvent = nativeEventParam || EMPTY_NATIVE_EVENT;
const inst = getInstanceFromNode(rootNodeID);
batchedUpdates(function() {
runExtractedEventsInBatch(
runExtractedPluginEventsInBatch(
topLevelType,
inst,
nativeEvent,

View File

@ -623,7 +623,10 @@ export function createFiberFromEventComponent(
const fiber = createFiber(EventComponent, pendingProps, key, mode);
fiber.elementType = eventComponent;
fiber.type = eventComponent;
fiber.stateNode = new Map();
fiber.stateNode = {
props: pendingProps,
state: null,
};
fiber.expirationTime = expirationTime;
return fiber;
}

View File

@ -774,6 +774,8 @@ function completeWork(
popHostContext(workInProgress);
const rootContainerInstance = getRootHostContainer();
const responder = workInProgress.type.responder;
// Update the props on the event component state node
workInProgress.stateNode.props = newProps;
handleEventComponent(responder, rootContainerInstance, workInProgress);
}
break;

View File

@ -53,7 +53,7 @@ function initTestRenderer() {
// This is a new feature in Fiber so I put it in its own test file. It could
// probably move to one of the other test files once it is official.
describe('ReactTopLevelText', () => {
describe('ReactFiberEvents', () => {
describe('NoopRenderer', () => {
beforeEach(() => {
initNoopRenderer();