The library for web and native user interfaces.
Use `isAncestorIDOf` in `ReactInstanceHandles`
This is both a functional fix and performance fix for `ReactInstanceHandles`. - `getFirstReactDOM` uses `indexOf` but should be checking ancestry (via `isMarker()`). - Added `isAncestorIDOf`, checking ancestry can be way faster than getting a common ancestor: http://jsperf.com/react-ancestor-id-check
CommitSyncScript committed
Jun 10, 2013 at 16:16 UTC
93f979ae18c9decabd378c33a45cda21c92765ec
2 files changed
+40
-12
src/core/ReactInstanceHandles.js
+22
-12
@@ -58,7 +58,7 @@ function getReactRootIDString(index) {
58
* @return {boolean} True if the character is a separator or end of the ID.
59
* @private
60
*/
61
-function isMarker(id, index) {
61
+function isBoundary(id, index) {
62
return id.charAt(index) === SEPARATOR || index === id.length;
63
}
64
@@ -75,6 +75,21 @@ function isValidID(id) {
75
);
76
}
77
78
+/**
79
+ * Checks if the first ID is an ancestor of or equal to the second ID.
80
+ *
81
+ * @param {string} ancestorID
82
+ * @param {string} descendantID
83
+ * @return {boolean} True if `ancestorID` is an ancestor of `descendantID`.
84
+ * @internal
85
+ */
86
+function isAncestorIDOf(ancestorID, descendantID) {
87
+ return (
88
+ descendantID.indexOf(ancestorID) === 0 &&
89
+ isBoundary(descendantID, ancestorID.length)
90
+ );
91
+}
92
+
93
/**
94
* Gets the parent ID of the supplied React DOM ID, `id`.
95
*
@@ -102,12 +117,8 @@ function getNextDescendantID(ancestorID, destinationID) {
117
ancestorID,
118
destinationID
119
);
105
- var longestCommonID = getFirstCommonAncestorID(
106
- ancestorID,
107
- destinationID
108
- );
120
invariant(
110
- longestCommonID === ancestorID,
121
+ isAncestorIDOf(ancestorID, destinationID),
122
'getNextDescendantID(...): React has made an invalid assumption about ' +
123
'the DOM hierarchy. Expected `%s` to be an ancestor of `%s`.',
124
ancestorID,
@@ -120,7 +131,7 @@ function getNextDescendantID(ancestorID, destinationID) {
131
// another separator or we reach the end of `destinationID`.
132
var start = ancestorID.length + SEPARATOR_LENGTH;
133
for (var i = start; i < destinationID.length; i++) {
123
- if (isMarker(destinationID, i)) {
134
+ if (isBoundary(destinationID, i)) {
135
break;
136
}
137
}
@@ -146,7 +157,7 @@ function getFirstCommonAncestorID(oneID, twoID) {
157
var lastCommonMarkerIndex = 0;
158
// Use `<=` to traverse until the "EOL" of the shorter string.
159
for (var i = 0; i <= minLength; i++) {
149
- if (isMarker(oneID, i) && isMarker(twoID, i)) {
160
+ if (isBoundary(oneID, i) && isBoundary(twoID, i)) {
161
lastCommonMarkerIndex = i;
162
} else if (oneID.charAt(i) !== twoID.charAt(i)) {
163
break;
@@ -182,10 +193,9 @@ function traverseParentPath(start, stop, cb, arg, skipFirst, skipLast) {
193
'traverseParentPath(...): Cannot traverse from and to the same ID, `%s`.',
194
start
195
);
185
- var ancestorID = getFirstCommonAncestorID(start, stop);
186
- var traverseUp = ancestorID === stop;
196
+ var traverseUp = isAncestorIDOf(stop, start);
197
invariant(
188
- traverseUp || ancestorID === start,
198
+ traverseUp || isAncestorIDOf(start, stop),
199
'traverseParentPath(%s, %s, ...): Cannot traverse from two IDs that do ' +
200
'not have a parent path.',
201
start,
@@ -277,7 +287,7 @@ var ReactInstanceHandles = {
287
while (child) {
288
if (id === child.id) {
289
return child;
280
- } else if (id.indexOf(child.id) === 0) {
290
+ } else if (isAncestorIDOf(child.id, id)) {
291
return ReactInstanceHandles.findComponentRoot(child, id);
292
}
293
child = child.nextSibling;
src/core/__tests__/ReactInstanceHandles-test.js
+18
@@ -84,6 +84,24 @@ describe('ReactInstanceHandles', function() {
84
});
85
});
86
87
+ describe('findComponentRoot', function() {
88
+ it('should find the correct node with prefix sibling IDs', function() {
89
+ var parentNode = document.createElement('div');
90
+ var childNodeA = document.createElement('div');
91
+ var childNodeB = document.createElement('div');
92
+ parentNode.appendChild(childNodeA);
93
+ parentNode.appendChild(childNodeB);
94
+
95
+ parentNode.id = '.react[0]';
96
+ childNodeA.id = '.react[0].0';
97
+ childNodeB.id = '.react[0].0:1';
98
+
99
+ expect(
100
+ ReactInstanceHandles.findComponentRoot(parentNode, childNodeB.id)
101
+ ).toBe(childNodeB);
102
+ });
103
+ });
104
+
105
describe('getReactRootIDFromNodeID', function() {
106
it('should support strings', function() {
107
var test = '.reactRoot[s_0_1][0]..[1]';