Build up a native component cache for event dispatching
Changes to event overloading structure
Sebastian Markbage committed
Mar 30, 2016 at 14:35 UTC
3287d932351638ed5d9c658bc071189efba227d0
8 files changed
+217
-17
src/renderers/native/ReactIOS/IOSNativeBridgeEventPlugin.js
+7
-11
@@ -41,25 +41,21 @@ var IOSNativeBridgeEventPlugin = {
41
eventTypes: merge(customBubblingEventTypes, customDirectEventTypes),
42
43
/**
44
- * @param {string} topLevelType Record from `EventConstants`.
45
- * @param {DOMEventTarget} topLevelTarget The listening component root node.
46
- * @param {string} topLevelTargetID ID of `topLevelTarget`.
47
- * @param {object} nativeEvent Native browser event.
48
- * @return {*} An accumulation of synthetic events.
44
* @see {EventPluginHub.extractEvents}
45
*/
46
extractEvents: function(
52
- topLevelType: string,
53
- topLevelTarget: EventTarget,
54
- topLevelTargetID: string,
55
- nativeEvent: Event
47
+ topLevelType,
48
+ targetInst,
49
+ nativeEvent,
50
+ nativeEventTarget
51
): ?Object {
52
var bubbleDispatchConfig = customBubblingEventTypes[topLevelType];
53
var directDispatchConfig = customDirectEventTypes[topLevelType];
54
var event = SyntheticEvent.getPooled(
55
bubbleDispatchConfig || directDispatchConfig,
61
- topLevelTargetID,
62
- nativeEvent
56
+ targetInst,
57
+ nativeEvent,
58
+ nativeEventTarget
59
);
60
if (bubbleDispatchConfig) {
61
EventPropagators.accumulateTwoPhaseDispatches(event);
src/renderers/native/ReactNative/ReactNativeBaseComponent.js
+4
-1
@@ -13,6 +13,7 @@
13
14
var NativeMethodsMixin = require('NativeMethodsMixin');
15
var ReactNativeAttributePayload = require('ReactNativeAttributePayload');
16
+var ReactNativeComponentTree = require('ReactNativeComponentTree');
17
var ReactNativeEventEmitter = require('ReactNativeEventEmitter');
18
var ReactNativeTagHandles = require('ReactNativeTagHandles');
19
var ReactMultiChild = require('ReactMultiChild');
@@ -57,6 +58,7 @@ ReactNativeBaseComponent.Mixin = {
58
},
59
60
unmountComponent: function() {
61
+ ReactNativeComponentTree.uncacheNode(this);
62
deleteAllListeners(this._rootNodeID);
63
this.unmountChildren();
64
this._rootNodeID = null;
@@ -196,7 +198,6 @@ ReactNativeBaseComponent.Mixin = {
198
);
199
200
var nativeTopRootTag = nativeContainerInfo._tag;
199
- console.log('mountInCmp', nativeContainerInfo, nativeTopRootTag);
201
UIManager.createView(
202
tag,
203
this.viewConfig.uiViewClassName,
@@ -204,6 +205,8 @@ ReactNativeBaseComponent.Mixin = {
205
updatePayload
206
);
207
208
+ ReactNativeComponentTree.precacheNode(this, tag);
209
+
210
this._registerListenersUponCreation(this._currentElement.props);
211
this.initializeChildren(
212
this._currentElement.props.children,
src/renderers/native/ReactNative/ReactNativeComponentTree.js
new
+66
@@ -0,0 +1,66 @@
1
+/**
2
+ * Copyright 2013-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule ReactNativeComponentTree
10
+ */
11
+
12
+'use strict';
13
+
14
+var invariant = require('invariant');
15
+
16
+var instanceCache = {};
17
+
18
+/**
19
+ * Drill down (through composites and empty components) until we get a native or
20
+ * native text component.
21
+ *
22
+ * This is pretty polymorphic but unavoidable with the current structure we have
23
+ * for `_renderedChildren`.
24
+ */
25
+function getRenderedNativeOrTextFromComponent(component) {
26
+ var rendered;
27
+ while ((rendered = component._renderedComponent)) {
28
+ component = rendered;
29
+ }
30
+ return component;
31
+}
32
+
33
+/**
34
+ * Populate `_nativeNode` on the rendered native/text component with the given
35
+ * DOM node. The passed `inst` can be a composite.
36
+ */
37
+function precacheNode(inst, tag) {
38
+ var nativeInst = getRenderedNativeOrTextFromComponent(inst);
39
+ instanceCache[tag] = nativeInst;
40
+}
41
+
42
+function uncacheNode(inst) {
43
+ var tag = inst._rootNodeID;
44
+ if (tag) {
45
+ delete instanceCache[tag];
46
+ }
47
+}
48
+
49
+function getInstanceFromTag(tag) {
50
+ return instanceCache[tag] || null;
51
+}
52
+
53
+function getTagFromInstance(inst) {
54
+ invariant(inst._rootNodeID, 'All native instances should have a tag.');
55
+ return inst._rootNodeID;
56
+}
57
+
58
+var ReactNativeComponentTree = {
59
+ getClosestInstanceFromNode: getInstanceFromTag,
60
+ getInstanceFromNode: getInstanceFromTag,
61
+ getNodeFromInstance: getTagFromInstance,
62
+ precacheNode: precacheNode,
63
+ uncacheNode: uncacheNode,
64
+};
65
+
66
+module.exports = ReactNativeComponentTree;
src/renderers/native/ReactNative/ReactNativeDefaultInjection.js
+4
@@ -30,7 +30,9 @@ var ReactNativeComponentEnvironment = require('ReactNativeComponentEnvironment')
30
var ReactNativeGlobalInteractionHandler = require('ReactNativeGlobalInteractionHandler');
31
var ReactNativeGlobalResponderHandler = require('ReactNativeGlobalResponderHandler');
32
var ReactNativeTextComponent = require('ReactNativeTextComponent');
33
+var ReactNativeTreeTraversal = require('ReactNativeTreeTraversal');
34
var ReactNativeComponent = require('ReactNativeComponent');
35
+var ReactNativeComponentTree = require('ReactNativeComponentTree');
36
var ReactSimpleEmptyComponent = require('ReactSimpleEmptyComponent');
37
var ReactUpdates = require('ReactUpdates');
38
var ResponderEventPlugin = require('ResponderEventPlugin');
@@ -47,6 +49,8 @@ function inject() {
49
* Inject module for resolving DOM hierarchy and plugin ordering.
50
*/
51
EventPluginHub.injection.injectEventPluginOrder(IOSDefaultEventPluginOrder);
52
+ EventPluginUtils.injection.injectComponentTree(ReactNativeComponentTree);
53
+ EventPluginUtils.injection.injectTreeTraversal(ReactNativeTreeTraversal);
54
55
ResponderEventPlugin.injection.injectGlobalResponderHandler(
56
ReactNativeGlobalResponderHandler
src/renderers/native/ReactNative/ReactNativeEventEmitter.js
+4
-3
@@ -14,6 +14,7 @@
14
var EventPluginHub = require('EventPluginHub');
15
var EventPluginRegistry = require('EventPluginRegistry');
16
var ReactEventEmitterMixin = require('ReactEventEmitterMixin');
17
+var ReactNativeComponentTree = require('ReactNativeComponentTree');
18
var ReactNativeTagHandles = require('ReactNativeTagHandles');
19
var EventConstants = require('EventConstants');
20
@@ -112,15 +113,15 @@ var ReactNativeEventEmitter = merge(ReactEventEmitterMixin, {
113
* @param {object} nativeEventParam Object passed from native.
114
*/
115
_receiveRootNodeIDEvent: function(
115
- rootNodeID: ?string,
116
+ rootNodeID: number,
117
topLevelType: string,
118
nativeEventParam: Object
119
) {
120
var nativeEvent = nativeEventParam || EMPTY_NATIVE_EVENT;
121
+ var inst = ReactNativeComponentTree.getInstanceFromNode(rootNodeID);
122
ReactNativeEventEmitter.handleTopLevel(
123
topLevelType,
122
- rootNodeID,
123
- rootNodeID,
124
+ inst,
125
nativeEvent,
126
nativeEvent.target
127
);
src/renderers/native/ReactNative/ReactNativeTextComponent.js
+5
-1
@@ -11,6 +11,7 @@
11
12
'use strict';
13
14
+var ReactNativeComponentTree = require('ReactNativeComponentTree');
15
var ReactNativeTagHandles = require('ReactNativeTagHandles');
16
var UIManager = require('UIManager');
17
@@ -43,6 +44,9 @@ Object.assign(ReactNativeTextComponent.prototype, {
44
nativeTopRootTag,
45
{text: this._stringText}
46
);
47
+
48
+ ReactNativeComponentTree.precacheNode(this, tag);
49
+
50
return tag;
51
},
52
@@ -56,7 +60,6 @@ Object.assign(ReactNativeTextComponent.prototype, {
60
var nextStringText = '' + nextText;
61
if (nextStringText !== this._stringText) {
62
this._stringText = nextStringText;
59
- console.log('receiveComponent', this, this._rootNodeID);
63
UIManager.updateView(
64
this._rootNodeID,
65
'RCTRawText',
@@ -67,6 +70,7 @@ Object.assign(ReactNativeTextComponent.prototype, {
70
},
71
72
unmountComponent: function() {
73
+ ReactNativeComponentTree.uncacheNode(this);
74
this._currentElement = null;
75
this._stringText = null;
76
this._rootNodeID = null;
src/renderers/native/ReactNative/ReactNativeTreeTraversal.js
new
+126
@@ -0,0 +1,126 @@
1
+/**
2
+ * Copyright 2015-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule ReactNativeTreeTraversal
10
+ */
11
+
12
+'use strict';
13
+
14
+// Same as ReactDOMTreeTraversal without the invariants.
15
+
16
+/**
17
+ * Return the lowest common ancestor of A and B, or null if they are in
18
+ * different trees.
19
+ */
20
+function getLowestCommonAncestor(instA, instB) {
21
+ var depthA = 0;
22
+ for (var tempA = instA; tempA; tempA = tempA._nativeParent) {
23
+ depthA++;
24
+ }
25
+ var depthB = 0;
26
+ for (var tempB = instB; tempB; tempB = tempB._nativeParent) {
27
+ depthB++;
28
+ }
29
+
30
+ // If A is deeper, crawl up.
31
+ while (depthA - depthB > 0) {
32
+ instA = instA._nativeParent;
33
+ depthA--;
34
+ }
35
+
36
+ // If B is deeper, crawl up.
37
+ while (depthB - depthA > 0) {
38
+ instB = instB._nativeParent;
39
+ depthB--;
40
+ }
41
+
42
+ // Walk in lockstep until we find a match.
43
+ var depth = depthA;
44
+ while (depth--) {
45
+ if (instA === instB) {
46
+ return instA;
47
+ }
48
+ instA = instA._nativeParent;
49
+ instB = instB._nativeParent;
50
+ }
51
+ return null;
52
+}
53
+
54
+/**
55
+ * Return if A is an ancestor of B.
56
+ */
57
+function isAncestor(instA, instB) {
58
+ while (instB) {
59
+ if (instB === instA) {
60
+ return true;
61
+ }
62
+ instB = instB._nativeParent;
63
+ }
64
+ return false;
65
+}
66
+
67
+/**
68
+ * Return the parent instance of the passed-in instance.
69
+ */
70
+function getParentInstance(inst) {
71
+ return inst._nativeParent;
72
+}
73
+
74
+/**
75
+ * Simulates the traversal of a two-phase, capture/bubble event dispatch.
76
+ */
77
+function traverseTwoPhase(inst, fn, arg) {
78
+ var path = [];
79
+ while (inst) {
80
+ path.push(inst);
81
+ inst = inst._nativeParent;
82
+ }
83
+ var i;
84
+ for (i = path.length; i-- > 0;) {
85
+ fn(path[i], false, arg);
86
+ }
87
+ for (i = 0; i < path.length; i++) {
88
+ fn(path[i], true, arg);
89
+ }
90
+}
91
+
92
+/**
93
+ * Traverses the ID hierarchy and invokes the supplied `cb` on any IDs that
94
+ * should would receive a `mouseEnter` or `mouseLeave` event.
95
+ *
96
+ * Does not invoke the callback on the nearest common ancestor because nothing
97
+ * "entered" or "left" that element.
98
+ */
99
+function traverseEnterLeave(from, to, fn, argFrom, argTo) {
100
+ var common = from && to ? getLowestCommonAncestor(from, to) : null;
101
+ var pathFrom = [];
102
+ while (from && from !== common) {
103
+ pathFrom.push(from);
104
+ from = from._nativeParent;
105
+ }
106
+ var pathTo = [];
107
+ while (to && to !== common) {
108
+ pathTo.push(to);
109
+ to = to._nativeParent;
110
+ }
111
+ var i;
112
+ for (i = 0; i < pathFrom.length; i++) {
113
+ fn(pathFrom[i], true, argFrom);
114
+ }
115
+ for (i = pathTo.length; i-- > 0;) {
116
+ fn(pathTo[i], false, argTo);
117
+ }
118
+}
119
+
120
+module.exports = {
121
+ isAncestor: isAncestor,
122
+ getLowestCommonAncestor: getLowestCommonAncestor,
123
+ getParentInstance: getParentInstance,
124
+ traverseTwoPhase: traverseTwoPhase,
125
+ traverseEnterLeave: traverseEnterLeave,
126
+};
src/renderers/native/ReactNative/findNodeHandle.js
+1
-1
@@ -79,7 +79,7 @@ function findNodeHandle(componentOrHandle: any): ?number {
79
// ReactInstanceMap.get here will always succeed for mounted components
80
var internalInstance = ReactInstanceMap.get(component);
81
if (internalInstance) {
82
- return internalInstance._rootNodeID;
82
+ return internalInstance.getNativeNode();
83
} else {
84
var rootNodeID = component._rootNodeID;
85
if (rootNodeID) {