@samitouri / QOS-React / commits / 3287d93235

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) {