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. :)
Sebastian Markbåge committed
Jun 13, 2018 at 16:20 UTC
051637da615a4e8d0cbf8f147a75e129c1866138
8 files changed
+85
-22
packages/react-native-renderer/src/ReactFabric.js
+2
-2
@@ -20,7 +20,7 @@ import ReactVersion from 'shared/ReactVersion';
20
21
import NativeMethodsMixin from './NativeMethodsMixin';
22
import ReactNativeComponent from './ReactNativeComponent';
23
-import * as ReactNativeComponentTree from './ReactNativeComponentTree';
23
+import * as ReactFabricComponentTree from './ReactFabricComponentTree';
24
import {getInspectorDataForViewTag} from './ReactNativeFiberInspector';
25
26
import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState';
@@ -120,7 +120,7 @@ const ReactFabric: ReactFabricType = {
120
};
121
122
ReactFabricRenderer.injectIntoDevTools({
123
- findFiberByHostInstance: ReactNativeComponentTree.getClosestInstanceFromNode,
123
+ findFiberByHostInstance: ReactFabricComponentTree.getClosestInstanceFromNode,
124
getInspectorDataForViewTag: getInspectorDataForViewTag,
125
bundleType: __DEV__ ? 1 : 0,
126
version: ReactVersion,
packages/react-native-renderer/src/ReactFabricComponentTree.js
new
+28
@@ -0,0 +1,28 @@
1
+/**
2
+ * Copyright (c) 2013-present, Facebook, Inc.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+import invariant from 'fbjs/lib/invariant';
9
+
10
+function getInstanceFromInstance(instanceHandle) {
11
+ return instanceHandle;
12
+}
13
+
14
+function getTagFromInstance(inst) {
15
+ let tag = inst.stateNode.canonical._nativeTag;
16
+ invariant(tag, 'All native instances should have a tag.');
17
+ return tag;
18
+}
19
+
20
+export {
21
+ getInstanceFromInstance as getClosestInstanceFromNode,
22
+ getInstanceFromInstance as getInstanceFromNode,
23
+ getTagFromInstance as getNodeFromInstance,
24
+};
25
+
26
+export function getFiberCurrentPropsFromNode(inst) {
27
+ return inst.canonical.currentProps;
28
+}
packages/react-native-renderer/src/ReactFabricHostConfig.js
+2
@@ -293,6 +293,8 @@ export function prepareUpdate(
293
);
294
// TODO: If the event handlers have changed, we need to update the current props
295
// in the commit phase but there is no host config hook to do it yet.
296
+ // So instead we hack it by updating it in the render phase.
297
+ instance.canonical.currentProps = newProps;
298
return updatePayload;
299
}
300
packages/react-native-renderer/src/ReactFabricInjection.js
+3
-11
@@ -9,15 +9,7 @@
9
10
import './ReactNativeInjectionShared';
11
12
-// TODO: The event emitter registration is interfering with the existing
13
-// ReactNative renderer. So disable it for Fabric for now.
12
+import * as ReactFabricComponentTree from './ReactFabricComponentTree';
13
+import * as EventPluginUtils from 'events/EventPluginUtils';
14
15
-// import * as ReactNativeEventEmitter from './ReactNativeEventEmitter';
16
-
17
-// Module provided by RN:
18
-// import RCTEventEmitter from 'RCTEventEmitter';
19
-
20
-/**
21
- * Register the event emitter with the native bridge
22
- */
23
-// RCTEventEmitter.register(ReactNativeEventEmitter);
15
+EventPluginUtils.injection.injectComponentTree(ReactFabricComponentTree);
packages/react-native-renderer/src/ReactNativeComponentTree.js
+1
-6
@@ -20,12 +20,7 @@ export function uncacheFiberNode(tag) {
20
}
21
22
function getInstanceFromTag(tag) {
23
- if (typeof tag === 'number') {
24
- return instanceCache[tag] || null;
25
- } else {
26
- // Fabric will invoke event emitters on a direct fiber reference
27
- return tag;
28
- }
23
+ return instanceCache[tag] || null;
24
}
25
26
function getTagFromInstance(inst) {
packages/react-native-renderer/src/ReactNativeInjection.js
+4
@@ -9,6 +9,8 @@
9
10
import './ReactNativeInjectionShared';
11
12
+import * as ReactNativeComponentTree from './ReactNativeComponentTree';
13
+import * as EventPluginUtils from 'events/EventPluginUtils';
14
import * as ReactNativeEventEmitter from './ReactNativeEventEmitter';
15
16
// Module provided by RN:
@@ -18,3 +20,5 @@ import RCTEventEmitter from 'RCTEventEmitter';
20
* Register the event emitter with the native bridge
21
*/
22
RCTEventEmitter.register(ReactNativeEventEmitter);
23
+
24
+EventPluginUtils.injection.injectComponentTree(ReactNativeComponentTree);
packages/react-native-renderer/src/ReactNativeInjectionShared.js
-3
@@ -17,11 +17,9 @@
17
import 'InitializeCore';
18
19
import * as EventPluginHub from 'events/EventPluginHub';
20
-import * as EventPluginUtils from 'events/EventPluginUtils';
20
import ResponderEventPlugin from 'events/ResponderEventPlugin';
21
22
import ReactNativeBridgeEventPlugin from './ReactNativeBridgeEventPlugin';
24
-import * as ReactNativeComponentTree from './ReactNativeComponentTree';
23
import ReactNativeEventPluginOrder from './ReactNativeEventPluginOrder';
24
import ReactNativeGlobalResponderHandler from './ReactNativeGlobalResponderHandler';
25
@@ -29,7 +27,6 @@ import ReactNativeGlobalResponderHandler from './ReactNativeGlobalResponderHandl
27
* Inject module for resolving DOM hierarchy and plugin ordering.
28
*/
29
EventPluginHub.injection.injectEventPluginOrder(ReactNativeEventPluginOrder);
32
-EventPluginUtils.injection.injectComponentTree(ReactNativeComponentTree);
30
31
ResponderEventPlugin.injection.injectGlobalResponderHandler(
32
ReactNativeGlobalResponderHandler,
packages/react-native-renderer/src/__tests__/ReactFabric-test.internal.js
+45
@@ -352,4 +352,49 @@ describe('ReactFabric', () => {
352
11,
353
);
354
});
355
+
356
+ it('dispatches events to the last committed props', () => {
357
+ const View = createReactNativeComponentClass('RCTView', () => ({
358
+ validAttributes: {},
359
+ uiViewClassName: 'RCTView',
360
+ directEventTypes: {
361
+ topTouchStart: {
362
+ registrationName: 'onTouchStart',
363
+ },
364
+ },
365
+ }));
366
+
367
+ const touchStart = jest.fn();
368
+ const touchStart2 = jest.fn();
369
+
370
+ ReactFabric.render(<View onTouchStart={touchStart} />, 11);
371
+
372
+ expect(FabricUIManager.createNode.mock.calls.length).toBe(1);
373
+ expect(FabricUIManager.registerEventHandler.mock.calls.length).toBe(1);
374
+
375
+ let [, , , , instanceHandle] = FabricUIManager.createNode.mock.calls[0];
376
+ let [dispatchEvent] = FabricUIManager.registerEventHandler.mock.calls[0];
377
+
378
+ let touchEvent = {
379
+ touches: [],
380
+ changedTouches: [],
381
+ };
382
+
383
+ expect(touchStart).not.toBeCalled();
384
+
385
+ dispatchEvent(instanceHandle, 'topTouchStart', touchEvent);
386
+
387
+ expect(touchStart).toBeCalled();
388
+ expect(touchStart2).not.toBeCalled();
389
+
390
+ ReactFabric.render(<View onTouchStart={touchStart2} />, 11);
391
+
392
+ // Intentionally dispatch to the same instanceHandle again.
393
+ dispatchEvent(instanceHandle, 'topTouchStart', touchEvent);
394
+
395
+ // The current semantics dictate that we always dispatch to the last committed
396
+ // props even though the actual scheduling of the event could have happened earlier.
397
+ // This could change in the future.
398
+ expect(touchStart2).toBeCalled();
399
+ });
400
});