Skip to content

Commit

Permalink
Build up a native component cache for event dispatching
Browse files Browse the repository at this point in the history
Changes to event overloading structure
  • Loading branch information
sebmarkbage committed Apr 20, 2016
1 parent 75cec60 commit 3287d93
Show file tree
Hide file tree
Showing 8 changed files with 217 additions and 17 deletions.
18 changes: 7 additions & 11 deletions src/renderers/native/ReactIOS/IOSNativeBridgeEventPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,25 +41,21 @@ var IOSNativeBridgeEventPlugin = {
eventTypes: merge(customBubblingEventTypes, customDirectEventTypes),

/**
* @param {string} topLevelType Record from `EventConstants`.
* @param {DOMEventTarget} topLevelTarget The listening component root node.
* @param {string} topLevelTargetID ID of `topLevelTarget`.
* @param {object} nativeEvent Native browser event.
* @return {*} An accumulation of synthetic events.
* @see {EventPluginHub.extractEvents}
*/
extractEvents: function(
topLevelType: string,
topLevelTarget: EventTarget,
topLevelTargetID: string,
nativeEvent: Event
topLevelType,
targetInst,
nativeEvent,
nativeEventTarget
): ?Object {
var bubbleDispatchConfig = customBubblingEventTypes[topLevelType];
var directDispatchConfig = customDirectEventTypes[topLevelType];
var event = SyntheticEvent.getPooled(
bubbleDispatchConfig || directDispatchConfig,
topLevelTargetID,
nativeEvent
targetInst,
nativeEvent,
nativeEventTarget
);
if (bubbleDispatchConfig) {
EventPropagators.accumulateTwoPhaseDispatches(event);
Expand Down
5 changes: 4 additions & 1 deletion src/renderers/native/ReactNative/ReactNativeBaseComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

var NativeMethodsMixin = require('NativeMethodsMixin');
var ReactNativeAttributePayload = require('ReactNativeAttributePayload');
var ReactNativeComponentTree = require('ReactNativeComponentTree');
var ReactNativeEventEmitter = require('ReactNativeEventEmitter');
var ReactNativeTagHandles = require('ReactNativeTagHandles');
var ReactMultiChild = require('ReactMultiChild');
Expand Down Expand Up @@ -57,6 +58,7 @@ ReactNativeBaseComponent.Mixin = {
},

unmountComponent: function() {
ReactNativeComponentTree.uncacheNode(this);
deleteAllListeners(this._rootNodeID);
this.unmountChildren();
this._rootNodeID = null;
Expand Down Expand Up @@ -196,14 +198,15 @@ ReactNativeBaseComponent.Mixin = {
);

var nativeTopRootTag = nativeContainerInfo._tag;
console.log('mountInCmp', nativeContainerInfo, nativeTopRootTag);
UIManager.createView(
tag,
this.viewConfig.uiViewClassName,
nativeTopRootTag,
updatePayload
);

ReactNativeComponentTree.precacheNode(this, tag);

this._registerListenersUponCreation(this._currentElement.props);
this.initializeChildren(
this._currentElement.props.children,
Expand Down
66 changes: 66 additions & 0 deletions src/renderers/native/ReactNative/ReactNativeComponentTree.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ReactNativeComponentTree
*/

'use strict';

var invariant = require('invariant');

var instanceCache = {};

/**
* Drill down (through composites and empty components) until we get a native or
* native text component.
*
* This is pretty polymorphic but unavoidable with the current structure we have
* for `_renderedChildren`.
*/
function getRenderedNativeOrTextFromComponent(component) {
var rendered;
while ((rendered = component._renderedComponent)) {
component = rendered;
}
return component;
}

/**
* Populate `_nativeNode` on the rendered native/text component with the given
* DOM node. The passed `inst` can be a composite.
*/
function precacheNode(inst, tag) {
var nativeInst = getRenderedNativeOrTextFromComponent(inst);
instanceCache[tag] = nativeInst;
}

function uncacheNode(inst) {
var tag = inst._rootNodeID;
if (tag) {
delete instanceCache[tag];
}
}

function getInstanceFromTag(tag) {
return instanceCache[tag] || null;
}

function getTagFromInstance(inst) {
invariant(inst._rootNodeID, 'All native instances should have a tag.');
return inst._rootNodeID;
}

var ReactNativeComponentTree = {
getClosestInstanceFromNode: getInstanceFromTag,
getInstanceFromNode: getInstanceFromTag,
getNodeFromInstance: getTagFromInstance,
precacheNode: precacheNode,
uncacheNode: uncacheNode,
};

module.exports = ReactNativeComponentTree;
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ var ReactNativeComponentEnvironment = require('ReactNativeComponentEnvironment')
var ReactNativeGlobalInteractionHandler = require('ReactNativeGlobalInteractionHandler');
var ReactNativeGlobalResponderHandler = require('ReactNativeGlobalResponderHandler');
var ReactNativeTextComponent = require('ReactNativeTextComponent');
var ReactNativeTreeTraversal = require('ReactNativeTreeTraversal');
var ReactNativeComponent = require('ReactNativeComponent');
var ReactNativeComponentTree = require('ReactNativeComponentTree');
var ReactSimpleEmptyComponent = require('ReactSimpleEmptyComponent');
var ReactUpdates = require('ReactUpdates');
var ResponderEventPlugin = require('ResponderEventPlugin');
Expand All @@ -47,6 +49,8 @@ function inject() {
* Inject module for resolving DOM hierarchy and plugin ordering.
*/
EventPluginHub.injection.injectEventPluginOrder(IOSDefaultEventPluginOrder);
EventPluginUtils.injection.injectComponentTree(ReactNativeComponentTree);
EventPluginUtils.injection.injectTreeTraversal(ReactNativeTreeTraversal);

ResponderEventPlugin.injection.injectGlobalResponderHandler(
ReactNativeGlobalResponderHandler
Expand Down
7 changes: 4 additions & 3 deletions src/renderers/native/ReactNative/ReactNativeEventEmitter.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
var EventPluginHub = require('EventPluginHub');
var EventPluginRegistry = require('EventPluginRegistry');
var ReactEventEmitterMixin = require('ReactEventEmitterMixin');
var ReactNativeComponentTree = require('ReactNativeComponentTree');
var ReactNativeTagHandles = require('ReactNativeTagHandles');
var EventConstants = require('EventConstants');

Expand Down Expand Up @@ -112,15 +113,15 @@ var ReactNativeEventEmitter = merge(ReactEventEmitterMixin, {
* @param {object} nativeEventParam Object passed from native.
*/
_receiveRootNodeIDEvent: function(
rootNodeID: ?string,
rootNodeID: number,
topLevelType: string,
nativeEventParam: Object
) {
var nativeEvent = nativeEventParam || EMPTY_NATIVE_EVENT;
var inst = ReactNativeComponentTree.getInstanceFromNode(rootNodeID);
ReactNativeEventEmitter.handleTopLevel(
topLevelType,
rootNodeID,
rootNodeID,
inst,
nativeEvent,
nativeEvent.target
);
Expand Down
6 changes: 5 additions & 1 deletion src/renderers/native/ReactNative/ReactNativeTextComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

'use strict';

var ReactNativeComponentTree = require('ReactNativeComponentTree');
var ReactNativeTagHandles = require('ReactNativeTagHandles');
var UIManager = require('UIManager');

Expand Down Expand Up @@ -43,6 +44,9 @@ Object.assign(ReactNativeTextComponent.prototype, {
nativeTopRootTag,
{text: this._stringText}
);

ReactNativeComponentTree.precacheNode(this, tag);

return tag;
},

Expand All @@ -56,7 +60,6 @@ Object.assign(ReactNativeTextComponent.prototype, {
var nextStringText = '' + nextText;
if (nextStringText !== this._stringText) {
this._stringText = nextStringText;
console.log('receiveComponent', this, this._rootNodeID);
UIManager.updateView(
this._rootNodeID,
'RCTRawText',
Expand All @@ -67,6 +70,7 @@ Object.assign(ReactNativeTextComponent.prototype, {
},

unmountComponent: function() {
ReactNativeComponentTree.uncacheNode(this);
this._currentElement = null;
this._stringText = null;
this._rootNodeID = null;
Expand Down
126 changes: 126 additions & 0 deletions src/renderers/native/ReactNative/ReactNativeTreeTraversal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/**
* Copyright 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ReactNativeTreeTraversal
*/

'use strict';

// Same as ReactDOMTreeTraversal without the invariants.

/**
* Return the lowest common ancestor of A and B, or null if they are in
* different trees.
*/
function getLowestCommonAncestor(instA, instB) {
var depthA = 0;
for (var tempA = instA; tempA; tempA = tempA._nativeParent) {
depthA++;
}
var depthB = 0;
for (var tempB = instB; tempB; tempB = tempB._nativeParent) {
depthB++;
}

// If A is deeper, crawl up.
while (depthA - depthB > 0) {
instA = instA._nativeParent;
depthA--;
}

// If B is deeper, crawl up.
while (depthB - depthA > 0) {
instB = instB._nativeParent;
depthB--;
}

// Walk in lockstep until we find a match.
var depth = depthA;
while (depth--) {
if (instA === instB) {
return instA;
}
instA = instA._nativeParent;
instB = instB._nativeParent;
}
return null;
}

/**
* Return if A is an ancestor of B.
*/
function isAncestor(instA, instB) {
while (instB) {
if (instB === instA) {
return true;
}
instB = instB._nativeParent;
}
return false;
}

/**
* Return the parent instance of the passed-in instance.
*/
function getParentInstance(inst) {
return inst._nativeParent;
}

/**
* Simulates the traversal of a two-phase, capture/bubble event dispatch.
*/
function traverseTwoPhase(inst, fn, arg) {
var path = [];
while (inst) {
path.push(inst);
inst = inst._nativeParent;
}
var i;
for (i = path.length; i-- > 0;) {
fn(path[i], false, arg);
}
for (i = 0; i < path.length; i++) {
fn(path[i], true, arg);
}
}

/**
* Traverses the ID hierarchy and invokes the supplied `cb` on any IDs that
* should would receive a `mouseEnter` or `mouseLeave` event.
*
* Does not invoke the callback on the nearest common ancestor because nothing
* "entered" or "left" that element.
*/
function traverseEnterLeave(from, to, fn, argFrom, argTo) {
var common = from && to ? getLowestCommonAncestor(from, to) : null;
var pathFrom = [];
while (from && from !== common) {
pathFrom.push(from);
from = from._nativeParent;
}
var pathTo = [];
while (to && to !== common) {
pathTo.push(to);
to = to._nativeParent;
}
var i;
for (i = 0; i < pathFrom.length; i++) {
fn(pathFrom[i], true, argFrom);
}
for (i = pathTo.length; i-- > 0;) {
fn(pathTo[i], false, argTo);
}
}

module.exports = {
isAncestor: isAncestor,
getLowestCommonAncestor: getLowestCommonAncestor,
getParentInstance: getParentInstance,
traverseTwoPhase: traverseTwoPhase,
traverseEnterLeave: traverseEnterLeave,
};
2 changes: 1 addition & 1 deletion src/renderers/native/ReactNative/findNodeHandle.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ function findNodeHandle(componentOrHandle: any): ?number {
// ReactInstanceMap.get here will always succeed for mounted components
var internalInstance = ReactInstanceMap.get(component);
if (internalInstance) {
return internalInstance._rootNodeID;
return internalInstance.getNativeNode();
} else {
var rootNodeID = component._rootNodeID;
if (rootNodeID) {
Expand Down

0 comments on commit 3287d93

Please sign in to comment.