@samitouri / QOS-React-2 / commits / 93f979ae18
Fork 0

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]';