Extract Fabric event handlers from canonical props (#13024)

We need a different "component tree" thingy for Fabric.

A lot of this doesn't really make much sense in a persistent world but
currently we can't dispatch events to memoizedProps on a Fiber since
they're pooled. Also, it's unclear what the semantics should be when we
dispatch an event that happened when the old props were in effect but now
we have new props already.

This implementation tries to use the last committed props but also fails
at that because we don't have a commit hook in the persistent mode.

However, at least it doesn't crash when dispatching. :)
This commit is contained in:
Sebastian Markbåge
2018-06-13 16:20:48 -07:00
committed by GitHub
parent 2a8085980f
commit 051637da61
8 changed files with 85 additions and 22 deletions
+2 -2
View File
@@ -20,7 +20,7 @@ import ReactVersion from 'shared/ReactVersion';
import NativeMethodsMixin from './NativeMethodsMixin';
import ReactNativeComponent from './ReactNativeComponent';
import * as ReactNativeComponentTree from './ReactNativeComponentTree';
import * as ReactFabricComponentTree from './ReactFabricComponentTree';
import {getInspectorDataForViewTag} from './ReactNativeFiberInspector';
import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState';
@@ -120,7 +120,7 @@ const ReactFabric: ReactFabricType = {
};
ReactFabricRenderer.injectIntoDevTools({
findFiberByHostInstance: ReactNativeComponentTree.getClosestInstanceFromNode,
findFiberByHostInstance: ReactFabricComponentTree.getClosestInstanceFromNode,
getInspectorDataForViewTag: getInspectorDataForViewTag,
bundleType: __DEV__ ? 1 : 0,
version: ReactVersion,
@@ -0,0 +1,28 @@
/**
* 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.
*/
import invariant from 'fbjs/lib/invariant';
function getInstanceFromInstance(instanceHandle) {
return instanceHandle;
}
function getTagFromInstance(inst) {
let tag = inst.stateNode.canonical._nativeTag;
invariant(tag, 'All native instances should have a tag.');
return tag;
}
export {
getInstanceFromInstance as getClosestInstanceFromNode,
getInstanceFromInstance as getInstanceFromNode,
getTagFromInstance as getNodeFromInstance,
};
export function getFiberCurrentPropsFromNode(inst) {
return inst.canonical.currentProps;
}
@@ -293,6 +293,8 @@ export function prepareUpdate(
);
// TODO: If the event handlers have changed, we need to update the current props
// in the commit phase but there is no host config hook to do it yet.
// So instead we hack it by updating it in the render phase.
instance.canonical.currentProps = newProps;
return updatePayload;
}
+3 -11
View File
@@ -9,15 +9,7 @@
import './ReactNativeInjectionShared';
// TODO: The event emitter registration is interfering with the existing
// ReactNative renderer. So disable it for Fabric for now.
import * as ReactFabricComponentTree from './ReactFabricComponentTree';
import * as EventPluginUtils from 'events/EventPluginUtils';
// import * as ReactNativeEventEmitter from './ReactNativeEventEmitter';
// Module provided by RN:
// import RCTEventEmitter from 'RCTEventEmitter';
/**
* Register the event emitter with the native bridge
*/
// RCTEventEmitter.register(ReactNativeEventEmitter);
EventPluginUtils.injection.injectComponentTree(ReactFabricComponentTree);
@@ -20,12 +20,7 @@ export function uncacheFiberNode(tag) {
}
function getInstanceFromTag(tag) {
if (typeof tag === 'number') {
return instanceCache[tag] || null;
} else {
// Fabric will invoke event emitters on a direct fiber reference
return tag;
}
return instanceCache[tag] || null;
}
function getTagFromInstance(inst) {
@@ -9,6 +9,8 @@
import './ReactNativeInjectionShared';
import * as ReactNativeComponentTree from './ReactNativeComponentTree';
import * as EventPluginUtils from 'events/EventPluginUtils';
import * as ReactNativeEventEmitter from './ReactNativeEventEmitter';
// Module provided by RN:
@@ -18,3 +20,5 @@ import RCTEventEmitter from 'RCTEventEmitter';
* Register the event emitter with the native bridge
*/
RCTEventEmitter.register(ReactNativeEventEmitter);
EventPluginUtils.injection.injectComponentTree(ReactNativeComponentTree);
@@ -17,11 +17,9 @@
import 'InitializeCore';
import * as EventPluginHub from 'events/EventPluginHub';
import * as EventPluginUtils from 'events/EventPluginUtils';
import ResponderEventPlugin from 'events/ResponderEventPlugin';
import ReactNativeBridgeEventPlugin from './ReactNativeBridgeEventPlugin';
import * as ReactNativeComponentTree from './ReactNativeComponentTree';
import ReactNativeEventPluginOrder from './ReactNativeEventPluginOrder';
import ReactNativeGlobalResponderHandler from './ReactNativeGlobalResponderHandler';
@@ -29,7 +27,6 @@ import ReactNativeGlobalResponderHandler from './ReactNativeGlobalResponderHandl
* Inject module for resolving DOM hierarchy and plugin ordering.
*/
EventPluginHub.injection.injectEventPluginOrder(ReactNativeEventPluginOrder);
EventPluginUtils.injection.injectComponentTree(ReactNativeComponentTree);
ResponderEventPlugin.injection.injectGlobalResponderHandler(
ReactNativeGlobalResponderHandler,
@@ -352,4 +352,49 @@ describe('ReactFabric', () => {
11,
);
});
it('dispatches events to the last committed props', () => {
const View = createReactNativeComponentClass('RCTView', () => ({
validAttributes: {},
uiViewClassName: 'RCTView',
directEventTypes: {
topTouchStart: {
registrationName: 'onTouchStart',
},
},
}));
const touchStart = jest.fn();
const touchStart2 = jest.fn();
ReactFabric.render(<View onTouchStart={touchStart} />, 11);
expect(FabricUIManager.createNode.mock.calls.length).toBe(1);
expect(FabricUIManager.registerEventHandler.mock.calls.length).toBe(1);
let [, , , , instanceHandle] = FabricUIManager.createNode.mock.calls[0];
let [dispatchEvent] = FabricUIManager.registerEventHandler.mock.calls[0];
let touchEvent = {
touches: [],
changedTouches: [],
};
expect(touchStart).not.toBeCalled();
dispatchEvent(instanceHandle, 'topTouchStart', touchEvent);
expect(touchStart).toBeCalled();
expect(touchStart2).not.toBeCalled();
ReactFabric.render(<View onTouchStart={touchStart2} />, 11);
// Intentionally dispatch to the same instanceHandle again.
dispatchEvent(instanceHandle, 'topTouchStart', touchEvent);
// The current semantics dictate that we always dispatch to the last committed
// props even though the actual scheduling of the event could have happened earlier.
// This could change in the future.
expect(touchStart2).toBeCalled();
});
});